从零开始的C++学习生活 4:类和对象(下)
个人主页:Yupureki-CSDN博客
C++专栏:C++_Yupureki的博客-CSDN博客
目录
前言
1. 再谈构造函数
2. 类型转换
3. static成员
static成员变量:
static成员函数:
总结:
4. 友元
友元函数:
友元类:
5. 内部类
6. 匿名对象
7. 对象拷贝时的编译器优化
常见优化场景:
示例:
总结
前言
如果说上一章我们学会了如何搭建一个“毛坯房”,那么本章——《类和对象(下)》——我们将要学习的,就是如何对它进行精装修、建立小区公共设施、并制定灵活的访客规则。我们将深入挖掘C++为对象模型提供的更精密、更强大的控制机制。
1. 再谈构造函数
之前我们用构造函数初始化变量都是在函数体内完成的,而我们有另一种方式,那就是利用初始化列表。
class A
{
public:A(int a1 = 1, int a2 = 2):_a1(a1)//初始化列表,_a2(a2){cout << "A(int)" << endl;}
private:int _a1;int _a2;
}
我们会发现,初始化列表在构造函数的大括号外,再用传递的形参a1和a2的值进行初始化。
并且初始化列表还有以下特性:
• 每个成员变量在初始化列表中只能出现⼀次
• 引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始 化,否则会编译报错。
• C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的 成员使⽤的。
class A
{
public:A(int a1, int a2 = 3):_a1(a1)//如果函数参数(a1)没有缺省值,那么_a1初始化为声明处给的缺省值(1),_a2(a2)//如果函数参数(a2)有缺省值,那么_a2初始化为a2的值{cout << "A(int)" << endl;}
private:int _a1 = 1;//C++11之后可在变量声明处给缺省值int _a2 = 2;
}
• 尽量使用初始化列表初始化,因为那些你不在初始化列表初始化的成员也会⾛初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没 有显⽰在初始化列表初始化的内置类型成员是否初始化取决于编译器
• 初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序⽆ 关。建议声明顺序和初始化列表顺序保持⼀致。
class A
{
public:A(int a1 = 1):_a1(a1),_a2(_a1)//_a1还没开始初始化,为随机值。_a2先初始化为_a1的值为随机值{cout << "A(int)" << endl;}
private:int _a2;//_a2先声明 那么初始化时_a2先初始化int _a1;
}
初始化列表总结:
无论是否显示写初始化列表,每个构造函数都有初始化列表;
无论是否在初始化列表显示初始化成员变量,每个成员变量都要走初始化列表初始化;
2. 类型转换
除了C语言中我们学到的的几种类型转换,在C++中还有其他几种
C++ 允许隐式类型转换,特别是从内置类型到类类型的转换:
构造函数前面加explicit就不再支持隐式类型转换。
类类型的对象之间也可以隐式转换,需要相应的构造函数支持。
class A {
public:A(int a) : _a(a) {}
private:int _a;
};A a = 1; // 隐式转换:1 -> A(1)
使用 explicit
关键字可以禁止隐式转换:
explicit A(int a) :_a(a) {}
// A a = 1; // 错误:不能隐式转换
A a(1); // 正确:显式构造
3. static成员
类中能包含用static修饰的成员函数或者成员变量,静态成员属于类本身,而非某个对象
static成员变量:
1.静态成员变量⼀定要在类外进行初始化。
class A {
public:A() { //count = 1 错误,static成员变量不能在类内初始化count++; }
private:static int count; // 声明
};int A::count = 0; // 定义并初始化
2. 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
class B {
public:void Add(){b++;}void Print(){cout << b << endl;}
private:static int b;
};
int B::b = 1;int main()
{B b1;B b2;b1.Print();b2.Print();b1.Add();b2.Print();return 0;
}
3. 静态成员也是类的成员,受public、protected、private访问限定符的限制。
4. 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员 变量不属于某个对象,不⾛构造函数初始化列表。
static成员函数:
1. 静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针。非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
static void Print(){cout << b2 << endl;cout << b1 << endl;//static成员函数不能访问非static成员变量}void print(){cout<<b1<<b2<<endl;//普通成员函数都可以访问}
private:static int b2;int b1 = 1;
};
int B::b2 = 2;
2. 突破类域就可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量 和静态成员函数。
总结:
-
静态成员变量必须在类外定义;
-
静态成员函数没有 this 指针,只能访问静态成员;
-
可通过
类名::成员
或对象.成员
访问。
4. 友元
友元提供了⼀种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类 声明的前⾯加friend,并且把友元声明放到⼀个类的里面。
友元函数:
1. 外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。
class A {friend void Print(const A& a);
private:int _a = 10;
};void Print(const A& a) {cout << a._a << endl; // 可以访问私有成员
}
2. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
3. ⼀个函数可以是多个类的友元函数。
友元类:
1. 友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
class B {friend class C; // C 是 B 的友元类
private:int _b = 20;
};class C {
public:void ShowB(const B& b) {cout << b._b << endl; // 可以访问 B 的私有成员}
};
2. 友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
如上图代码,C是B的友元类,能访问B的私有成员,但B不是C的友元类,不能访问C
3. 友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是C的友元。
5. 内部类
如果⼀个类定义在另⼀个类的内部,这个里面的小类就叫做内部类,外面的大类叫做外部类。
内部类默认是外部类的友元类,因此可以内部类可以访问外部类的成员
class Outer {//外部类
private:static int _k;int _h = 1;
public:class Inner {//内部类public:void Show(const Outer& o) {cout << _k << endl; // 可访问静态成员cout << o._h << endl; // 可访问实例成员}};
};
6. 匿名对象
匿名对象是一种临时对象,生命周期仅限于当前行:
class A {
public:A(int a = 0) : _a(a) {}~A() { cout << "析构" << endl; }
};A(); // 匿名对象,本行结束即析构
A(10); // 带参匿名对象
Solution().Sum_Solution(10); // 常用场景:调用临时对象的函数
匿名对象主要是方便快捷,如果我们只想访问一个类中的成员函数,放以前我们只能专门创造一个对象然后访问。利用匿名对象我们无需创造类就可以直接访问
class C {
public:C(int _c1 = 2):c1(_c1){ }void Print(){cout << c1 << endl;}
private:int c1 = 1;
};int main()
{C c1 = 2;c1.Print();C(2).Print();//匿名对象可以直接访问成员函数
}
7. 对象拷贝时的编译器优化
现代编译器会对对象的构造和拷贝进行优化,减少不必要的临时对象
常见优化场景:
-
连续构造 + 拷贝构造 → 合并为一次构造;
-
返回值优化(RVO/NRVO):将返回的临时对象与接收对象合并。
示例:
A f2() {A aa;return aa; // 可能被优化为直接构造在调用处
}int main(){// 传值传参// 构造+拷⻉构造A aa1;f1(aa1);f1(1);// 隐式类型,连续构造+拷⻉构造->优化为直接构造f1(A(2)); // ⼀个表达式中,连续构造+拷⻉构造->优化为⼀个构造cout << endl;
}
如何优化C++标准并没有严格规定,各个编译器会根据情况自行处理。当前主流的相对新⼀点的编 译器对于连续⼀个表达式步骤中的连续拷贝会进行合并优化,有些更新更"激进"的编译器还会进行跨行跨表达式的合并优化。
总结
掌握类和对象的高级特性,能写出更高效、更安全的C++代码。尤其是:
-
初始化列表是构造函数的真正起点;
-
静态成员实现类的共享资源;
-
友元和内部类提供灵活的封装突破;
-
匿名对象和编译器优化提升运行时效率。