本文主要是介绍【 C++ 】类和对象的学习(三),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
前言:
😘我的主页:OMGmyhair-CSDN博客
目录
一、初始化列表
二、类型转换
三、static成员
四、友元
五、内部类
六、匿名对象
一、初始化列表
当我们之前在写构造函数时,我们通常在构造函数内对成员变量进行赋值。但其实还有一种方法是对成员变量进行初始化,那就是初始化列表:
2.引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始化。
这个也很好理解。像这三种变量,它们本身必须在定义时就进行初始化。而初始化列表就是它们定义的地方,所以它们在初始化列表中必须进行初始化:
(1)引用变量
对于未定义时未初始化的引用变量,编译器要求对齐进行初始化。
(2)const成员变量
至于被const修饰的的变量,定义初始化后就不能再改变,也就不能再赋值,所以必须在定义时就进行初始化。
(3)没有默认构造的类类型变量
如果一个类没有默认构造,那么它的对象在定义时就必须进行初始化,否则没有合适的构造函数供它使用。
对这三类变量正确的初始化可以如下:
class A
{
public:A(int x){_a = x;}private:int _a;
};class Date
{
public:Date(int& y,int date=20140101): year(y),_date(date),a(2014){}
private:int& year;const int _date;A a;
};
3.在C++11中支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的。
首先来看下面的一段代码:
class Date
{
public:Date(int year = 2014, int month = 12, int day = 3): _year(),_day(13){}void Print(){cout << _year << "/" << _month << "/" << _day << "/" <<_date<<endl;}private:int _year = 1000;int _month = 10;int _day = 10;int _date;
};int main()
{Date d1(2024, 9, 6);Date d2;cout << "d1日期为:";d1.Print();cout << "d2日期为:";d2.Print();return 0;
}
来看看结果和你想的是否一样:
(1).首先,我们要区分构造函数参数列表的缺省值和成员变量声明处的缺省值
参数列表的缺省值是给你在初始化对象时调用构造函数是否需要传参用的,而成员变量声明处的缺省值,是用来给没有初始化的成员变量用的。
如果你参数列表的值没有赋值给成员变量,那么成员变量的值不会受到改变,这就是为什么d1初始化时是(2024,9,6),但是d1和d2的实际结果相同。
(2).为什么_year的值是0而不是1000?为什么_date是随机值?
在C++其实int也有构造函数,在初始化列表中,我们显式写了_year(),这一过程编译器会调用int的默认构造函数将_year赋值为0。
而_date我们没有在初始化列表中显式写出,则不会调用构造函数,那么就是随机值。
(3)._month为什么是10?_day为什么是13而不是10。
在初始化列表中,我们没有显式写出_month,这时如果在声明中有缺省值,就会用缺省值对_month进行赋值。
而_day在初始化列表中被我们显式地写出,进行了初始化,就不会再去使用在声明中的缺省值。
4.对于没有显式在初始化列表初始化的自定义成员变量,编译器会调用它们自己的默认构造函数。如果没有默认构造函数,编译器会报错。
无论我们是否显式地写出初始化列表,每个构造函数都有初始化。无论我们是否在初始化中显式写出某个成员变量,每个成员变量都会走一遍初始化列表。
总结一下成员变量走初始化列表的各种情况:
5.初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。所以建议声明顺序和初始化列表顺序保持⼀致。
我们用一道题目来加深理解:
class A
{
public:A(int a):_a1(a), _a2(_a1){}void Print() {cout << _a1 << " " << _a2 << endl;}
private:int _a2 = 2;int _a1 = 2;
};
int main()
{A aa(1);aa.Print();
}
我们来看看上面代码的输出结果:
首先,初始化列表按照成员变量的声明顺序进行初始化。所以第一个进行初始化的成员变量是_a2,此时将_a1的值赋给_a2,但这个时候_a1还未进行初始化,因此_a1的值是随机值,所以_a2也是随机值。
第二个进行初始化的值是_a1,将a的值赋给_a1,而a的值为1,所以_a1的值也为1。
所以这道题的答案为1和随机值,选D。
二、类型转换
1.C++支持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。
在上面的代码中就发生了隐式类型转换。在A a=2这句代码中产生了一个A类型的临时变量,2赋值给这个临时变量,这个临时变量再赋值给a。换句话说,2构造了一个A类型的临时对象,这个临时对象拷贝构造给a,而编译器遇到连续构造加上拷贝构造会直接优化成直接构造,意思是直接对a进行构造不用临时对象。
我们还能对这个临时对象进行引用:
因为临时对象具有常性,而我们引用的就是临时对象,所以需要加上const。
在C++11后我们还能进行多个参数的类型转换:
2.我们还能进行不同类类型对象之间的转换,需要相应的构造函数支持
class B
{
};class A
{
public:A(int x){_a = x;}A(int x, int y){_a = x;_aa = y;}A(B b)//隐式转换需要相应的构造函数支持{_b = b;}
private:int _a;int _aa;B _b;
};int main()
{A a = { 1,1 };//不同类类型对象之间的隐式转换B ab;A aa = ab;return 0;
}
3.在构造函数前面加explicit就不再支持隐式转换:
可以看到加入了explicit关键字后,编译器对隐式转换进行了报错。
三、static成员
静态成员就是静态成员变量和静态成员函数,首先来看看它们共同的特性:
1.静态成员也是类的成员,受public、protected、private 访问限定符的限制。
2.突破类域就可以访问静态成员,可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数。
class A
{
public:static int GetX(){return _ax;}static int _az;private:static int _ax;static int _ay;
};int A::_ax=1;
int A::_ay=2;
int A::_az=3;int main()
{A a;cout << A::_az << endl;cout << A::GetX() << endl;cout << a._az << endl;cout << a.GetX << endl;
}
来看在类中静态成员变量的几个特性:
1.用static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进行初始化。
class Date
{
private:static int _year;static int _month;static int _day;
};int Date::_year;
int Date::_month;
int Date::_day;
使用静态成员的特性,我们来看三道题:
1.实现一个类,计算程序中创建出了多少的类对象
class Create
{
public:Create()//默认构造函数{_count++;}Create(const Create& c)//拷贝构造函数{_count++;}~Create(){_count--;}static int GetCount(){return _count;}private:static int _count;
};int Create::_count=0;
2.求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
class Sum
{
public:Sum(){_ret += _n;_n++;}static int GetRet(){return _ret;}private:static int _n;static int _ret;
};int Sum::_n = 1;
int Sum::_ret = 0;class Solution
{
public:int Sum_Solution(int n) {//调用n个构造函数Sum* p = new Sum[n];delete[] p;//释放申请空间return Sum::GetRet();}
};int main()
{cout << Solution().Sum_Solution(5) << endl;//使用匿名对象进行访问return 0;
}
3.设已经有A,B,C,D 4个类的定义,程序中A,B,C,D构造函数调用顺序为?()
设已经有A,B,C,D 4个类的定义,程序中A,B,C,D析构函数调用顺序为?()
C c;
int main()
{
A a;
B b;
static D d;
return 0;
}
选项:
四、友元
class Time;//Time的前置声明,否则Date不认识Time
class Date
{friend void Print(const Date& date, const Time& time);public:Date(int year = 2014, int month = 12, int day = 13){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};class Time
{friend void Print(const Date& date, const Time& time);
public:Time(int hour = 21, int minute = 18, int second = 13){_hour = hour;_minute = minute;_second = second;}private:int _hour;int _minute;int _second;
};void Print(const Date& date, const Time& time)
{cout << date._year << endl;cout << time._hour << endl;
}int main()
{Date a;Time b;Print(a,b);return 0;
}
再来看看友元类的特性:
1.友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
2.友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
3.友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是C的友元。
class Time
{friend class Date;
public:Time(int time=2129){_time = time;}private:int _time;
};class Date
{friend class Age;
public:Date(int date=20240906){_date = date;cout << _t._time << endl;//Date是Time的朋友,可以访问Time的私有}private:int _date;Time _t;
};class Age
{
public:Age(int age = 20){_age = age;cout << _d._date << endl;//Age是Date的朋友,可以访问Date的私有}private:int _age;Date _d;
};
但是这里的朋友是单向的,例如Date是Time的朋友,可以访问Time的私有。但是Time不是Date的朋友,不能访问Date的私有。
朋友关系不能连续。Date是Time的朋友,Age是Date的朋友,但是Age不是Time的朋友。
五、内部类
class Solution
{
public:class Sum{public:Sum(){_ret += _n;_n++;}};int Sum_Solution(int n) {//调用n个构造函数Sum* p = new Sum[n];delete[]p;return _ret;}private:static int _n;static int _ret;
};int Solution::_n=1;
int Solution::_ret=0;
六、匿名对象
七、编译器优化
现代编译器为了追求效率会在不影响正确性的前提下对传值或传参过程中产生的拷贝进行省略。大部分编译器会对一个表达式中的连续拷贝进行优化,还有一些编译器更加“激进”,会对一些跨行跨表达式进行优化。
在下面这条语句中就产生了连续拷贝:
实际过程:
编译器优化后:
我现在使用的是vs2022,所以在一些场景下,优化比较厉害(以下都是在debug版本下进行):
场景1:
真实情况:
其实在这一过程中相当于省掉了a这一对象,因为a的析构是在打印函数之前,这里相当于直接构造临时对象。
场景2:
即使我们对a进行了前置++操作,编译器还是省掉了构造a的这一过程,可以看出编译器还是很厉害的。
场景3:
如果这篇文章有帮助到你,请留下您珍贵的点赞、收藏+评论,这对于我将是莫大的鼓励!学海无涯,共勉!😘😊😗💕💕😗😊😘
这篇关于【 C++ 】类和对象的学习(三)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!