二. 类和对象
文章参考:
相较于C语言的面向过程编程,C++改为面向对象编程,万物皆对象。
1. 构成
类一般由两部分构成:
- 数据成员
- 成员函数
访问权限可划分为三种,不同权限对应着不同的访问和继承规则:
- public
- protectd
- private
一个简单的对象:
#include <iostream>
using namespace std;
class Student
{
private: // 参数域
long long id;
int age;
char* name;
char sex;
public:
Student(); // 空的构造函数
Student(long long id, int age, char* name, char sex); // 构造函数
~Student(); // 析构函数:用于删除对象
void setId(long long id);
long long getId();
};
Student::Student(){
cout<<"这是空的构造函数"<<endl;
}
Student::Student(long long id, int age, char* name, char sex)
{
this->id = id;
this->age = age;
this->name = name;
this->sex = sex;
}
Student::~Student()
{
delete this->name; // 释放内存
}
void Student::setId(long long id){
this->id = id;
}
long long Student::getId(){
return this->id;
}
int main(){
char* name = "zhangSan";
Student* stu = new Student(123456789l, 18, name, '1');
cout<< stu->getId() <<endl;
return 0;
}
2. 成员函数
2.1 普通成员函数
定义:普通成员函数在类内部声明,然后在类外部完成函数的定义
。注意在类外部定义类的成员函数时,需要以作用域来取ClassName::function()
。
EG:
class Student
{
private:
long long id;
int age;
char* name;
char sex;
public:
Student(long long id, int age, char* name, char sex); // 构造函数
~Student(); // 析构函数:用于删除对象
void setId(long long id);
long long getId();
};
Student::Student(){
cout<<"这是空的构造函数"<<endl;
}
Student::Student(long long id, int age, char* name, char sex)
{
this->id = id;
this->age = age;
this->name = name;
this->sex = sex;
}
Student::~Student()
{
delete this->name; // 释放内存
}
void Student::setId(long long id){
this->id = id;
}
long long Student::getId(){
return this->id;
}
注意:
- 必须是在类外定义,如果在类内部定义,那就是内联函数了。
2.2 内联成员函数
说明:所谓内联成员函数,和内联函数的效果一样,使用该函数的地方编译器会将该函数的代码复制过去,从而提高效率,用空间换时间,只不过这个内联函数是类的成员函数而已。
定义:分为两种:
-
隐式声明:将成员函数直接定义在类内部,就像Java里一样,这样默认就是内联成员函数。
class Score{ private: int mid_exam; int fin_exam; public: void setScore(int m, int f) // 定义在内部,所以是内联函数 { mid_exam = m; fin_exam = f; } void showScore() { cout << "期中成绩: " << mid_exam << endl; cout << "期末成绩:" << fin_exam << endl; } };
-
显式声明:在类外声明某成员函数时在前面加上
inline
关键字,从而显式声明其为内联成员函数。class Score{ private: int mid_exam; int fin_exam; public: void setScore(int m, int f); void getScore(); }; inline void Score::setScore(int m, int f){ mid_exam = m; fin_exam = f; } inline void Score::getScore(){ cout << "期中成绩: " << mid_exam << endl; cout << "期末成绩:" << fin_exam << endl; }
注意:在使用inline
定义内联成员函数时,必须将类的声明和定义放在同一个文件(或同一个头文件内),否则编译时将无法进行代码替换。
2.3 构造函数和析构函数
2.3.1 构造函数
说明:构造函数是一种特殊的成员函数,其存在的意义在于为对象分配空间,完成对象的初始化。
特点:
- 没有返回值。
- 命名与类一致。
- 参数随意,可以不带。
- 无需用户调用,建立对象时会自动调用。
- 如果没有创建,编译器会自动生成一个无参构造函数。
- 对于没有定义构造函数的类,可以使用成员初始化列表初始化
公有成员
。
EG:
class Student
{
private:
long long id;
int age;
char* name;
char sex;
public:
Student();
Student(long long id, int age, char* name, char sex); // 构造函数
};
Student::Student(){
cout<<"这是空的构造函数"<<endl;
}
Student::Student(long long id, int age, char* name, char sex)
{
this->id = id;
this->age = age;
this->name = name;
this->sex = sex;
}
成员初始化列表:
在对类进行初始化时,可以在构造函数内依次对成员赋值,也可以使用成员初始化列表对其进行赋值。
语法:
类名::构造函数名([参数表])[:(成员初始化列表)]{
// 函数体
}
EG:
class Student
{
private:
long long id;
int age;
char* name;
char sex;
public:
Student();
Student(long long id, int age, char* name, char sex); // 构造函数
Student(long long newId, int newAge, char* newName) : id(newId), age(newAge), name(newName), sex('1'){
cout << "使用成员初始化列表进行初始化" << endl;
}
};
Student::Student(){
cout<<"这是空的构造函数"<<endl;
}
Student::Student(long long id, int age, char* name, char sex)
{
this->id = id;
this->age = age;
this->name = name;
this->sex = sex;
}
注意:类成员是按照它们在类里面被声明的顺序初始化的,与其在成员初始化列表里面的顺序无关。
2.3.2 析构函数
说明:析构函数是用来撤销对象的,主要负责释放对象内部成员的空间,完成一些连带业务等。
特点:
- 名字与类名相同,但前面必须加一个
~
。 - 没有返回值和参数,无法重载。
- 无需用户调用,当撤销对象时,编译系统会自动调用析构函数。
- 如果没有定义,编译器将会创建一个默认的析构函数。
EG:
#include <iostream>
using namespace std;
class Student
{
private: // 参数域
long long id;
int age;
char* name;
char sex;
public:
~Student(); // 析构函数:用于删除对象
};
Student::~Student()
{
delete this->name; // 释放内存
}
int main(){
char* name = "zhangSan";
Student* stu = new Student(123456789l, 18, name, '1');
cout<< stu->getId() <<endl;
return 0;
}
调用逻辑:
- 如果是一个全局对象,当离开其作用域时,自动调用。
- 如果定义在函数内,函数结束时,自动调用。
- 如果使用
new
创建出来的,使用delete
手动释放。
2.3.3 拷贝构造函数
说明:是一种特殊的构造函数,但其形参为本类对象的引用,其本质上是根据一个已经存在的对象,来拷贝出一个新的对象。
EG:
#include <iostream>
using namespace std;
class Score
{
private:
int mid_exam;
int fin_exam;
public:
Score(const Score &score); // 拷贝构造函数
int getMidExam();
};
Score::Score(const Score &score){
this->mid_exam = score.mid_exam;
this->fin_exam = score.fin_exam;
}
int Score::getMidExam(){
return this->mid_exam;
}
int main(int argc, char* argv[]){
// 在C++中,NULL被强定义为0,在重载时有时会产生二义性。因此推荐使用nullptr,表示空指针。
Score *p = nullptr;
Score s1(80, 90);
Score s2(s1);
cout << s1.getMidExam() << endl; // 80
cout << s2.getMidExam() << endl; // 80
return 0;
}
注意:
- 在拷贝构造函数中,可以直接访问传入对象的所有属性,即使是private级别也可以直接访问。
浅拷贝与深拷贝:
拷贝构造函数分为两种:浅拷贝和深拷贝。
-
浅拷贝:这也是默认的拷贝构造函数。直接将传入对象的值依次赋予要创建对象的数据成员。但此时如果数据成员中有指针,就会导致两个对象的内部的指针成员指向同意块空间,如果有一个对象被撤销了,那这片空间也就释放了,此时如果撤销另一个对象,就会导致那片空间再次遭到释放,这会带来错误。
class Student{ public: Student(char *name1, float score1); ~Student(); private: char *name; float score; }; Student stu1("白", 89); // Student stu2 = stu1; 这句话会报错
-
深拷贝:将传入对象的值依次赋予要创建对象的数据成员。而对于指针类型变量,为其生成生存空间,随后赋值。
Student::Student(const Student& stu) { name = new char[strlen(stu.name) + 1]; if (name != 0) { strcpy(name, stu.name); score = stu.score; } }
3. 自引用指针this
this
指针保存了当前对象的地址,称为自引用指针。
Score(Score &s);
Score::Score(Score &s){
if (this == &s) // 比较地址
return ;
this = &s;
}
4. string类
说明:
对于字符串,C语言在string.h
头文件中封装了许多方法,用于操作以\0
结尾的字符串数组。
而在C++中,除了支持C语言提供的方法外,还提供了个字符串类:string
,其封装了对于字符串处理所需的常用操作。
使用前必须引入文件:#include <string>
字符串常用运算符:
= += > < >= <= == != [](访问对应下标的字符) >>(输入) <<(输出)
EG:
void stringFunc(){
string str1 = "abc";
string str2("123");
cout<< str1 + str2 <<endl; // abc123
cout<< (str1 == str2) <<endl; // 0
}
5. 静态成员
5.1 静态数据成员
定义:
在一个类中,如果某个成员被声明为static
,则该成员是静态数据成员,不论这个类建立多少个对象,所有对象都共享这一份静态数据成员,从而实现了不同对象之间的数据共享。
特点:
- 静态数据成员的初始化应在类外单独进行,且应在定义对象之前进行。(因为它要被所有该类的对象共享)
- 静态数据成员属于类而不是属于单独某一个对象,因此可以通过类名访问(实际上是作用域访问,前提是其被声明为static),即:
类名::静态数据成员名
。 - 静态数据成员和静态变量一样,在编译时创建并初始化。可以被由该类所创建的所有对象访问,访问方法为:
对象名.静态数据成员名
对象指针->静态成员数据名
5.2 静态成员函数
定义:
在一个类中,如果某个成员函数前有static
,那么该成员函数是静态成员函数,不论这个类建立多少个对象,所有对象都共享这一份静态成员函数。且静态成员函数的作用是处理静态数据成员,而不是为了进行数据通信。格式如下:
static 返回类型 函数名 (参数列表);
调用:
三种方式:
类名::静态成员函数名(实参表);
对象名.静态成员函数名(实参表);
对象指针->静态成员函数名(实参表);
特点:
- 一般情况下,静态成员函数不访问非静态成员。如果确实需要,只能通过
对象名/对象指针/对象引用
进行访问。 - 私有静态成员不能被类外部的函数和对象访问。
- 编译系统将静态成员函数定义为内连接,因此假如与当前文件相连接的其它文件中有同名函数,也不会与该静态成员函数发生冲突。
6. 友元
C++提供了私有成员和保护成员,而这些成员只能通过类的成员函数进行访问。但有时我们需要对私有/保护成员频繁进行访问操作,这时多次调用成员函数会带来较大的时间和空间开销,导致程序的效率降低。因此C++提供了友元来对私有/保护成员进行访问。友元包括:
- 友元函数
- 友元类
6.1 友元函数
简介:
友元函数有两种:
- 非成员函数
- 成员函数
通过友元函数,我们直接对指定类的私有、保护、公有成员进行访问。
6.1.1 非成员函数声明为友元函数
-
声明:在要访问的目标类内声明,最前面要加上
friend
关键字。 -
定义:在类的外部定义。虽然友元函数在目标类内声明,但它并不是成员函数,因此在类外进行定义时并不需要加上
类名::
。 -
EG:
#include <iostream> using namespace std; class People{ private: int id; int age; public: People(int id, int age); ~People(); // 友元函数 friend int getId(People &p); }; People::People(int id, int age) { this.id = id; this.age = age; } // 并不是成员函数,因此定义时无需加上"类名::"进行限定。 int getId(People &p){ return p.id; }
-
特点:
-
虽然友元函数在目标类内声明,但它并不是成员函数,因此在类外进行定义时并不需要加上
类名::
。 -
因为友元函数不是类的成员函数,因此无法直接访问类的数据成员,也无法通过
this
指针访问,必须通过作为入口参数
传递过来的对象名(或对象引用、对象指针)来访问类的数据成员。 -
一个函数可以同时称为多个类的友元函数。例如:
#include <iostream> using namespace std; // 预定义,否则下面友元函数会报错 class Student; class People{ private: int id; int age; public: People(int id, int age); ~People(); // 友元函数 friend void show_id(People &p, Student &s); }; class Student{ private: int id; int age; public: Student(int id, int age); ~Student(); // 友元函数 friend void show_id(People &p, Student &s); }; // 并不是成员函数,因此定义时无需加上"类名::"进行限定。 void show_id(People &p, Student &s){ cout << "people's id = " << p.id << endl; cout << "student's id = " << s.id << endl; }
-
6.1.2 成员函数声明为友元函数
-
作用:将成员函数声明为友元类,该函数不仅可以访问自己所在类对象中的私有成员,也可以通过
friend
访问目标类的私有成员。 -
EG:
#include <iostream> using namespace std; // 预定义,否则下面友元函数会报错 class Student; class People{ private: int id; int age; public: People(int id, int age); ~People(); // 友元函数 void show_id(Student &s); }; class Student{ private: int id; int age; public: Student(int id, int age); ~Student(); // 友元函数,也是People的成员函数 friend void show_id(Student &s); }; // 是成员函数,因此定义时需要加上"类名::"进行限定。 void People::show_id(Student &s){ cout << "people's id = " << this..id << endl; cout << "student's id = " << s.id << endl; }
-
特点:
- 因为是成员函数,所以在类外定义时需要加上
类名::
。 - 因为是成员函数,所以可以通过
this
指针访问当前对象的成员。
- 因为是成员函数,所以在类外定义时需要加上
6.2 友元类
可以见一个类声明为另一个类的友元。
class Y{
···
};
class X{
friend Y; //声明类Y为类X的友元类
};
通过将Y声明为X的友元类,将Y中所有的函数都变为X的友元函数,也就是说,Y中所有的函数都可以访问X都的所有成员。
注意:
友元关系不具有交换性
和传递性
。
7 类的嵌套
定义:将一个类作为另一个类的数据成员,就是类的嵌套。
EG:
class Y{
···
};
class X{
Y y;
···
};
8 共享数据的保护
8.1 常引用
定义:const 类型& 引用名
int a = 10;
// 常引用,无法对引用进行再次赋值
const int&b = a;
void func(int& m, int& n){} // 此函数内无法修改m、n的值
8.2 常对象
定义:类名 const 对象名[(参数表)];
const Person p(1, 18);
特点:
- 常对象在声明时必须赋初值,不能先声明再定义。
- 常对象只能调用它的常成员函数,不能调用普通成员函数。
8.3 常成员对象
8.3.1 常数据成员
使用const
修饰的数据对象,一旦赋值,无法修改。且只能通过成员初始化列表对该数据成员进行赋值
。
8.3.2 常成员函数
声明:类型 函数名 (参数列表) const;
特点:
- 在声明和定义时都要加上
const
,调用时不用。 - 使用
const
可以对重载参数进行区分。 - 既可以访问静态数据成员,也可以访问一般数据成员。
- 但是不能更新数据成员,也不能调用该类的普通成员函数。这是为了保证常成员函数不对类的数据成员进行修改操作。
EG:
class Date{
private:
int year;
int month;
int day;
public:
Date(int y, int m, int d) : year(y), month(m), day(d){}
void showDate();
void showDate() const;
};
void Date::showDate() {
//···
}
void Date::showDate() const {
//···
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具