「C++」类和对象最终回
目录
前言
初始化列表:
使用及特点:
总结:
案例分析:
类型转换
单参数构造函数:
多参数构造函数:
static成员
友元
内部类
匿名对象
特点
使用方法:
匿名对象使用实例补充:
对象拷贝时的编译器优化
后记
前言
欢迎大家来到小鸥的博客,本篇将带你了解构造函数的初始化列表,构造函数传参时的类型转换,static成员,友元,内部类以及匿名对象等相关内容~
初始化列表:
在前面学习的构造函数实现中,初始化成员变量时使用的都是在函数体内进行赋值的方法,而构造函数还可以通过初始化列表来进行成员变量的初始化。
使用及特点:
1. 初始化列表在函数参数列表后,以一个冒号开始,接着以逗号为分割,每个成员变量后面跟一括号来放初始值或者表达式:
//Date(int year, int month, int day)Date(int year = 1, int month = 1, int day = 1):_year(year)//(year + 1),_month(month),_day(day){}
其中初始化列表的括号中可以是一个表达式;
2. 每个成员变量在初始化列表中只能出现一次,这是因为初始化列表的性质可以认为是成员变量定义初始化的地方,就和函数的声明和定义一样,定义有且只有一个。
3. 引用成员变量,const成员变量,没有默认构造的类类型成员变量,都必须放在初始化列表中进行初始化,否则将会报错
原因:
- 引用在定义时必须指定引用对象(类似指针必须指定指向的地址);
- const修饰的变量必须在定义时赋值,后续也不能通过赋值来修改(const修饰具有常性);
- 类类型的成员变量,定义时会调用默认构造函数,若只存在普通构造函数,又不给参数的话,其定义就会报错,但初始化列表中给它初始值之后,就相当于调用了该类的普通构造函数(传参)
例:
此处的i 和a 就是引用和const成员变量,由于初始化列表的性质类似与定义,所以可以在初始化列表中修改,而不能在函数体中进行修改。
4. C++11后支持在成员变量声明的位置给缺省值,这个缺省值就是为了在当一个成员变量没有显示在初始化列表中时使用。
此时_day成员没有在初始化列表中,也没有在函数体中进行赋值,所以d1对象定义完成后,得到的_day结果为2.
注意
- 成员变量声明时的值是一个缺省值,而不是赋值,即只有既不在初始化列表中,也不在函数体中时,才会使用,和函数的缺省参数性质一样。
- 成员变量声明处的缺省值,和构造函数参数中的缺省值要注意区分,前者是未显示在初始化列表中时使用,而后者是为了构造函数没有传参时使用的。
5. 初始化列表中是按照声明时的顺序进行初始化(因为开空间时的存放顺序就是按照声明的顺序存放的)的,和成员在初始化列表中的先后顺序无关,但建议保持一致。
总结:
(尽量不在函数体中赋值)
- 每个构造函数都有初始化列表;
每个成员变量都会进行初始化列表:
显示写在初始化列表中的成员,直接进行初始化;
不显示写在初始化列表中的成员:
声明时有缺省值就用缺省值;
没有缺省值:
内置类型成员变量:不确定,看编译器,大概率为随机值;自定义类型成员变量:调用它的默认构造,没有则报错;
引用,const修饰,没有默认构造的类类型成员变量,必须显示写在初始化列表中进行初始化。
案例分析:
结果分析:
- 由声明顺序可知,_b先声明,所以先初始化,但由于此时_a还未进行初始化,所以_b初始化结果为零;
- _a初始化使用传值参数a的值进行初始化,所以结果为1;
- 综合可知:声明时成员变量的缺省值只有在对其自身初始化时起作用,_a在作为初始值为_b初始化时,还未进行初始化,所以_b结果为随机值,而不是_a的缺省值
类型转换
C++支持内置类型隐式转换为类类型对象,但要保证自定义类型兼容该内置类型,且类类型中存在兼容该内置类型的构造函数。
在构造函数前加上关键字explicit可以禁止该类型转换
单参数构造函数:
class A
{
public:void Print(){cout << _a << endl;}A(int i = 0):_a(i){}//拷贝构造函数A(const A& pa):_a(pa._a){}
private:int _a;
};class Stack
{
public:void Push(const A& a){//....}
private:A _arr[10];int top;
};int main()
{A aa1(1);//传参给构造函数进行正常构造aa1.Print();A aa3 = aa1;//调用拷贝构造// 隐式类型转换// 2先隐式转换为double,构造出一个A类型的临时对象,再将临时对象拷贝构造到aa2// 编译器中遇到构造+拷贝构造->优化为直接构造A aa2 = 2;aa2.Print();//A& raa2 = aa2;//引用//const A& raa3 = 2;//2类型转换产生临时变量具有常性,所以必须加上const才能引用Stack st;st.Push(aa1);st.Push(3);//int类型转换为A类型return 0;
}
上述代码中,构造函数加上explicit:
多参数构造函数:
class A
{
public:A(int a = 1, int b = 1):_a(a), _b(b){}private:int _a;int _b;
};
class Stack
{
public:void Push(const A& a){//....}
private:A _arr[10];int top;
};
int main()
{A aa1(2, 2);A aa2 = { 2,2 };Stack st1;st1.Push(aa1);st1.Push({ 2,2 });return 0;
}
参数为多个时,类型转换要用大括号括起来。
总结:C++支持隐式类型转换,是借助构造函数来进行的。
static成员
1. static修饰的成员变量,称为静态成员变量,静态成员变量必须在类外进行初始化;
2. 静态成员变量是该类的所有对象共享的,不单独属于某个对象,也不存在对象中,而是存在静态区中,所有对象都可以调用;
3. static修饰成员函数,叫做静态成员函数,静态成员函数不存在this指针;
4. 静态成员函数只能访问其他静态成员函数,由于没有this指针,所以不能访问非静态成员变量;
5. 非静态成员函数,可以随意访问静态成员变量和函数;
6. 静态成员也受public,private,protected访问限定符的限制;
7. 静态成员为public时,可通过 类名::静态成员 和 对象名::静态成员 两种方式来外部访问;
8. 静态成员声明时不能添加缺省值,因为静态成员要在类外部定义初始化,不属于某个对象,而声明时的缺省值是用于初始化列表进行初始化的,静态成员不走构造函数的初始化列表路线。
class A
{
public:static int _b;//开放静态成员,可外部访问static void func(){cout << _a << endl;cout << _b << endl;//cout << _c << endl;//3. 4. 静态成员函数没有this指针,无法调用非静态成员,报错}void Print(){cout << _a << endl;cout << _b << endl;cout << _c << endl;}void Set(int a){_a = a;}
private://1. 8. 声明时的缺省值用于初始化列表,而静态成员不在构造函数中定义,所以不能有缺省值//static int _i = 1;//错误static int _a;//私有静态成员int _c = 0;
};int A::_a = 1;
int A::_b = 1;int main()
{A aa1; //_a _b _caa1.Print();// 1 1 0A aa2;//2. 对象aa2修改静态成员_a后,aa1打印出来_a也随之改变aa2.Set(2); //_a _b _caa1.Print();// 2 1 0A::func();// 2 1aa2.func();return 0;
}
友元
友元分为友元函数和友元类,其提供了一种突破类访问限定符封装的方式,将函数声明或者类声明的前面加上friend关键字,并且将其放到一个类里面,就构成了友元声明。
- 外部友元函数可以访问类的private和protected的成员,友元函数只是一个声明,而不会成为该类的成员函数;
- 友元声明可以在类定义的任何地方声明,不受访问限定符的限制;
- 一个函数可以是多个类的友元函数;
- 友元类中的成员函数都可以是另一个类的友元函数,都可以访问另一个类中的private和protected成员;
- 友元类的关系是单向的,不具有交换性,若A类是B类的友元类,则B类可以访问A类的成员,但B类不是A类的友元类,就不能访问,除非也在B类中加上A类的友元声明;
- 友元关系不具有传递性,如果A类是B类的友元,B类是C类的友元,不代表A类就是C类的友元,而需要单独声明;
- 友元有时提供了一定的便利性,但友元会增加耦合度,破坏封装性,所以友元不宜多用。
class B;class A
{
public://PPrint是A类的友元函数,可以访问其方法和成员friend void PPrint(const A& aa, const B& bb);friend class B;A(char a = 'a', char b = 'a'):_a(a),_b(b){}void PrintA() const{cout << _a << endl;cout << _b << endl;}
private:char _a;char _b;
};
class B
{friend void PPrint(const A& aa, const B& bb);
public:B(char a = 'b', char b = 'b'):_a(a),_b(b){};void PrintB() const{cout << _a << endl;cout << _b << endl;}
private:char _a;char _b;
};
void PPrint(const A& aa, const B& bb)
{//友元可以访问private和protected成员cout << aa._a << endl;cout << bb._b << endl;
}
int main()
{A aa;B bb;PPrint(aa, bb);return 0;
}
内部类
如果一个类定义在另一个类的内部,则这个类成为该类的内部类。
- 内部类是一个独立的类,与定义在全局的类相比,只是受到了类域的限制和访问操作符的限制,所以一个类定义的对象中不会包含其内部类。
- 内部类默认是其外部类的友元类,反之不成立。
- 内部类也是一种封装,当A类和B类紧密关联,A类主要的作用就是为B类服务时,就可以设计为内部类,若放到private和protected中,那么A类就为B类的专属内部类,其它地方将无法使用。
class A
{
public://构造函数A(int a = 1):_a(a){cout << "A(int a = 1)" << endl;}//拷贝构造函数A(const A& aa){cout << "A(const A& aa)" << endl;_a = aa._a;}//析构函数~A(){cout << "~A()" << endl;_a = 0;}void PrintA(){cout << "void PrintA()" << endl;//B()._b;//不能直接调用B类的成员变量,需要友元声明B().PrintB(A());}
private:int _a = 1;//A类的私有内部类class B{public://friend class A;void PrintB(const A& aa){cout << "void PrintB(const A& aa)" << endl;//可以直接调用A类的成员变量,因为内部类默认为外部类的友元类cout << aa._a << endl;}private:int _b;};
};
int main()
{A a1(2);a1.PrintA();return 0;
}
运行结果:
图示结果分析可知,对象a1先构造,然后调用PrintA函数,PrintA函数中创建了一个匿名对象来调用B类中的函数PrintB,所以打印出来的结果为1;
也说明B作为内部类可以直接调用A类的成员变量,是A的友元类,但反之A默认不是B的友元,需要单独声明。
匿名对象
特点
- 用 类型(实参) 的方式定义出来的对象叫做匿名对象,而之前定义对象的方式 类型 对象名(实参) 定义出的对象叫有名对象
- 匿名对象的生命周期只在其所在的一行,当需要定义一个对象临时使用时,就可以定义为匿名对象。
使用方法:
类名().成员函数();
#include <iostream>
using namespace std;class A
{
public:A(int a = 1):_a(a){cout << "A(int a = 1)" << endl;}A(const A& aa){cout << "A(const A& aa)" << endl;_a = aa._a;}~A(){cout << "~A()" << endl;_a = 0;}void Print(){cout << "void Print()" << endl;}
private:int _a = 1;
}int main()
{A().Print();//当前行结束后,匿名对象就会销毁return 0;
}
匿名对象使用实例补充:
万能头文件:<bits/stdc++> 会将常用的头文件一并包含。
- 不建议日常使用,只在竞赛时节省时间时有用。
对象拷贝时的编译器优化
在不影响正确性的前提下,对连续的构造和拷贝构造进行优化合并,从而减少拷贝消耗,提高代码速度
图中aa原本为隐式类型转换,先构造再拷贝构造,但编译器优化为直接构造,从而提升效率。
后记
感谢各位读者的阅读,欸,有不足的地方还是请大家继续指正哈~
本期专栏:C++_海盗猫鸥的博客-CSDN博客
个人主页:海盗猫鸥-CSDN博客
感谢各位的关注~