类和对象进阶

类和对象进阶
flowwalker类和对象进阶
目录
**备注:**此处有重要考点:即构造函数、复制构造函数调用顺序(通常 print 测试)
构造函数&复制构造函数&析构函数
⚠️ Tips:
- 不定义任何构造函数(包括复制构造函数)的情况下,类会自动生成
构造函数
函数名与类名相同
类生成必须调用(未设计情况下自动生成无参构造函数,否则不生成,不一定需要自己写)
可以重载(即:一个类可有多个构造函数,编译器自动选择可行且唯一的)
无返回值 (⚠️ void也不能写!)
不分配空间,只初始化空间
对象一旦生成后,不能在其上执行构造函数
private 的构造函数不能用于初始化
无参构造函数,又称为默认构造函数(不管是自动生成的,还是人为创立的)
基本范式
1 | class Complex{ |
隐式转换
1 | Complex c1 = 7 |
🤨比较奇怪的现象:(有点类似于复制省略)



类型转换构造函数
只有一个参数,参数类型可为多样,能够起到强制类型转换的作用(把数字隐式转换为一个临时对象)
原因如下:
1 | class Complex{ |
🤨 Complex c2=12; // 是初始化语句!不是赋值!会调用复制构造函数吗?
可以采用explicit关键字防止隐式转换(不过vscode好像直接宰了)
1
2
3
4
5
6
7
8
9
10
11
12
13
14 class Complex{
public:
double real,imag;
explicit Complex(int i){...} //类型转换构造函数
Complex(double r,double i){...}
}
int main(){
Complex c1(7,8);
Complex c2=Complex(12);
// c1=9; //error!
c1=Complex(9);
cout<<c1.real<<","<<c1.imag<<endl;
return 0;
}
复制构造函数
属于构造函数的一种,也称为拷贝构造函数,只有一个参数,参数类型为本类的引用
- 不编写则编译器会自动生成-默认复制构造函数。(如果自己编写了,默认复制构造函数就不存在了,而且可以发现自己写的不一定要“复制“)
范式
1 | // 第一种 |
注意⚠️:
const版本接收范围更广一些,可以接收const Complex &a 和Complex &a,而无const版本只能接收后者(因可实现修改,无法保证const传入不被修改)
禁止以本类的对象作为唯一参数,即
Complex(Complex c){} \\ ❌error!因为调用时将生成一份,这一份又要生成一份,这一份又要生成一份…死循环
理论上,复制构造函数可以重载并调用最佳匹配,但是一般不建议
传统上:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyClass {
public:
MyClass() = default;
MyClass(MyClass&) { std::cout << "non-const lvalue\n"; }
MyClass(const MyClass&){ std::cout << "const lvalue\n"; }
};
int main() {
MyClass a;
const MyClass b;
MyClass c = a; // 非 const 左值 -> 调用 non-const lvalue
MyClass d = b; // const 左值 -> 调用 const lvalue
MyClass e = MyClass(); // 临时对象(右值) -> 调用 const lvalue(C++98/11 都如此,编译器优化可能会产生拷贝消除而不输出)
}输出:
1
2
3 non-const lvalue
const lvalue
const lvalue此外,自己编写的复制构造函数并不一定要做复制操作,但习惯上还是保持类似于复制操作为佳
重点:被调用的三种情况
1.当用一个对象去初始化同类的另一个对象
1 | Complex c2(c1); // copy constructor called |
⚠️:对象间用等号赋值不导致调用复制构造函数
2.类A的对象作为某个函数的参数
函数被调用时,将调用复制构造函数
1 | class A |
3.类A的对象作为某个函数的返回值
函数返回时,复制构造函数被调用
1 | class A |
析构函数
函数名即
~类名一个类最多一个
默认自动生成,定义后则不自动生成(缺省析构函数什么也不做,最多确认基类的析构函数被调用)
对象消亡时自动被调用
动态分配的构造函数要手动写析构函数
范式
~类名(){}
或
~类名(); //声明
类名::~类名(){} //定义
作用情况
delete 触发消亡→析构
作用范围结束触发消亡→析构
(如:main 函数结束变量消亡、局部范围(如函数和{})结束局部变量消亡)
- 作为函数形参→函数结尾消亡
(注:引用和指针形参不属于对象,消亡对实参对象无影响)
- 作为函数返回值→return的东西消亡
重点:生存周期
经典范例1:
1 | class Demo { |
output:
1 | id=1, Construct |
经典范例2:
1 |
|
output:
1 | copy constructor |
静态成员(static 关键字)
⚠️ 本质上属于全局变量,写进类里是为了形式上为一个整体,易于维护和理解
- 静态成员变量只有一份,为同类的所有对象共享
- 静态成员变量必须在类定义外面专门定义一下,
T 类名::变量名(可选择进一步直接赋值) - 静态成员函数不具体作用在某个对象上
sizeof()计算不包含静态成员- 静态成员函数不能调用this指针(因为静态成员不具体作用于某个对象)
范式
类名::成员名 不需对象即可访问
(甚至可以在还未有对象生成时访问一个类的静态成员;普通访问方式也适用,但无意义)
其他注意点
静态成员不具体作用于某个对象,因此:
- 静态成员函数内部不能访问非静态成员变量和函数**(因为它搞不清非静态成员是谁)
静态成员函数的真实参数个数就是程序写的个数(普通成员函数还要加上this指针)
实战范例
1 |
|
this指针
核心作用:指向成员函数所作用的对象,即指向当前对象自己的指针
(非静态成员函数中可以使用 this 来代表 指向调用该成员函数的当前对象的指针)
官方的,非静态成员函数中可以使用 this 来代表 指向该函数作用的对象的指针(作用的对象其实也就是当前的对象)
最最最重要的用途:
1 | class Complex(){ |
C 和 C++ 的类比
一些神奇的思考
1
2
3
4
5
6
7
8
9
10
11
12 class A {
int i;
public:
void Hello() { cout << "hello" << endl; }
}; // void Hello( A * this ) { cout << "hello" << endl; }
int main() {
A * p = NULL;
p->Hello(); // 底层转换为 Hello(p);
} //输出:hello
// 此刻不涉及指针的具体内容(Null),传入后无所谓
// 注:此做法可能会产生未定义行为
1
2
3
4
5
6
7
8
9
10
11
12
13
14 class A {
int i;
public:
void Hello() { cout << i << "hello" << endl; }
};
// void Hello( A * this ) { cout << this->i << "hello" << endl; }
// this若为NULL,则出错
int main() {
A * p = NULL;
p->Hello(); // Hello(p);
}
// 此时 this 为 Null ,但是要求访问 i ,错误!
**备注:**此处又为重要考点:即封闭类,为垒土成山的基石
封闭类
成员对象
一个类A的对象a作为另一个类B的成员变量,则a为B的成员对象
(可以说a这个B的成员变量是一个成员对象,也可以说a这个A的对象是B的成员对象)
封闭类基本定义
包含成员对象的类,即称为封闭类
经典的例子
1 | class CTyre{ |
初始化列表
1 | 类名::构造函数名(‘类型 参数’表):成员变量1(参数表),成员变量2(参数表),... |
初始化列表中的成员变量,既可以是成员对象,也可以是基本类型的成员变量
对于成员对象,“参数表”中放的就是成员对象构造函数的参数
对于基本类型的成员变量,“参数表”中即初始值
关键是要让编译器弄明白成员对象如何初始化,否则编译出错
参数可以是复杂表达式(全局函数、字符串拼接、复杂算数表达式…)
⚠️:初始化列表和{…}内赋值似乎作用相同,然而以下情况必须使用初始化列表!
const 成员**:必须在创建时初始化(因为不能赋值)
引用成员:必须在定义时绑定对象!
没有默认构造函数的类类型成员:未初始化不可赋值!
1 | // 参见如下示例 |
封闭类中构造和析构函数执行顺序
⚠️ 先拼组件,再合装为成品/先拆成组件,组件再拆成碎片
(只需记住上面👆这点即可)
构造:
- 先执行成员对象构造函数
- 再执行封闭类自己的构造函数
析构:
- 先执行封闭类的析构函数
- 然后再执行成员对象的析构函数
⚠️ 对象成员的构造函数调用次序与对象成员在类中的说明次序,与在初始化列表出现次序无关
友元
- 一言以蔽之,方便访问别类的私有成员(但此处不具有对称性,即
你认为我是你的朋友,但我不一定) - 友元关系不能传递!不能继承!(参见继承章节)
友元函数
一个类的友元函数可以访问这个类的私有成员
1 | // 一般的 |
请参看如下具体示例:
1 | class CCar; //声明下用 |
友元类
1 | class CCar{ |
⚠️ 你以为我是你的朋友,但我可不一定这么看










