一、再谈构造函数
🎨构造函数体赋值
以前构造函数,我们是在函数体内赋值的
class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; };
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值
在函数体内初始化相当于先初始化再赋值
但对于像const成员变量,必须在定义的时候同时初始化,如果在函数体内初始化就会报错 ——
为此我们引入了初始化列表,就是给成员变量找到一个依次定义处理的地方
🎨初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式
class Date { public: Date(int year, int month, int day) : _year(year) , _month(month) , _day(day) {} private: int _year; //声明 int _month; int _day; }; int main() { Date d1(2022, 2, 22); return 0; }
【注意】
每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
🔥 1.类中包含以下成员,必须放在初始化列表进行初始化。注意,初始化列表就是成员变量定义的地方!理解的关键
const成员变量
因为const成员变量必须在定义的时候同时初始化
引用成员变量
引用必须在定义的时候初始化,因为初始化列表就是成员变量定义的地方
举例:
class Date { public: Date(int year, int month, int x) : _N(10) , _ref(x) { _year = year; _ref++; } private : const int _N;//声明 int& _ref; int _year; int _month; int _day; }; int main() { int x = 0; Date d1(2022, 2, x); return 0; }
没有默认构造函数的自定义类型成员
没有默认构造函数,编译器调不动,需要在定义的时候自己显式的传参去调
哪什么情况下没有?比如我们自己写了一个构造函数,还是带参的,编译器不再自动生成的 —— 举例如下
class Time { public: Time(int hour) { _hour = hour; } private: int _hour; }; class Date { public: //要初始化_t 对象,只能通过初始化列表 Date(int year, int hour) :_t(hour) { _year =year; } private: int _year; Time _t; }; int main() { Date d(2022,1) return 0; }
牢记!!!!很重要
ps:所有成员都会在初始化列表进行初始化,在进入花括号{}之前都会先去调用初始化列表
初始化列表:成员变量定义的地方
main函数里:对象整体定义的地方
私有域:成员变量声明的地方
🔥 2.尽量使用初始化列表初始化,提高效率。对于自定义类型变量,一定会先使用初始化列表初始化
举例来说明:
class Time { public: Time(int hour = 0) { _hour = hour; } private: int _hour; }; class Date { public: //要初始化_t 对象,只能通过初始化列表 //Date(int year, int hour) // :_t(hour) //{ // _year = year; //} //在函数体内赋值,但是还是会先走初始化列表调用Time的默认构造,不健康 Date(int year, int hour) { _year = year; Time t(hour); _t = t; } private: int _year; Time _t; }; int main() { Date d(2022, 1); return 0; }
先调用了初始化列表去构造、再对Time构造Time t(hour)、再赋值_t = t —— 不高效
💚总结:内置类型成员,在函数体和在初始化列表初始化都可以;自定义类型的成员,建议在初始化列表初始化,这样更高效
🔥彩蛋:C++11为了在构造时候对内置类型不处理的情况打了补丁,即在声明的时候赋予了缺省值
缺省值最终给到了初始化列表
🔥 3. 初始化列表中的初始化顺序是在类中的声明次序,与其在初始化列表中的先后次序无关
class A { public: A(int a) :_a1(a) //第二 , _a2(_a1) //第一 {} void Print() { cout << _a1 << " " << _a2 << endl; } private: int _a2; //初始化顺序 - 声明顺序 int _a1; }; int main() { A aa(1); aa.Print(); } 问: A.输出 1 1 B.程序崩溃 C.编译不通过 D.输出1 随机值
选D, 运行结果如下
建议:变量声明顺序和初始化列表出现顺序一致,即可不出错
🎨explicit关键字
为什么一个整型能转换为日期类?
#include<iostream> using namespace std; class Date { public: Date(int year) :_year(year) { cout << "Date(int year)" << endl; } Date(const Date& dd) { _year = dd._year; cout << "Date(const Date& year)" << endl; } private: int _year; }; int main() { Date d1(2002);//直接调用构造 Date d2 = 2022;//思考? 隐式类型转化:构造 + 拷贝构造 + 优化 》直接调用构造 return 0; }
这其实是因为单参数的构造函数中发生了隐式类型转换
回忆之前的
// 隐式类型转换 - 相近类型 double d = 1.1; int i = d; const int& i = d;
忘记的可以去看看,点这里跳转:C++入门 里面有讲解
这里本来是用2022构造一个临时对象Date(2022)但是C++在连续的过程中,编译器会优化多个构造,合二为一,因此这里被优化为直接就是一个构造
相当于以下两句——
Date tmp(2022); //先构造 Date d2(tmp); //再拷贝构造
但是这样的可读性就会大大降低,用explicit修饰构造函数,将会禁止单参构造函数的隐式转换 ——
💚匿名对象
Date(2022,10,26); //匿名对象————声明周期只有这一行 Solution slt; slt.Sum_Solution(10);//有名对象调用,专门开辟了slt来调用 //匿名对象调用 Solution().Sum_Solution(10);//节省空间
二、static成员
🌍静态成员变量
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数
面试题:实现一个类,计算程序中创建出了多少个类对象
思路:如果我们要对同一个变量进行++,唯一想到的就是定义一个全局变量,但是这样如果有两个类共用一个count会有累加效应,且暴露在外面可能被人随意更改
这时候就要引入static成员
特性:
静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
静态成员函数没有隐藏的this指针,不能访问任何非静态成员
静态成员也是类的成员,受public、protected、private 访问限定符的限制
作为私有成员,怎么样在类外访问呢?只能提供一个公用的成员函数getCount。代码如下 ——
我们可以通过对象来访问成员函数getCount
#include<iostream> using namespace std; class A { public: A(int a = 0) : _a(a) { _scount++; } A(const A& aa) : _a(aa._a) { _scount++; } int getCount() { return _scount; } private: int _a; static int _scount; //静态成员变量 }; int A::_scount = 0; //静态成员变量必须在类外的全局定义 int main() { A a1; A a2; A a3(a2); cout << a1.getCount() << endl; //通过对象来访问成员函数 return 0; }
那么有没有更好的方式,不需要定义对象就可以获取到呢?这要引入我们的静态成员函数
🌍静态成员函数
❤️ 静态成员函数没有隐藏的this指针,只能访问静态成员变量和函数。不能访问任何非静态成员
class A { public: A(int a = 0) :_a(a) { _scount++; } A(const A& aa) :_a(aa._a) { _scount++; } //静态成员函数 -- 没有this指针,不能访问非静态的成员 static int GetCount() { return _scount; } private: int _a; //静态成员变量,属于整个类,存在于静态区,生命周期在整个程序运行期间 static int _scount; }; int A::_scount = 0; int main() { A a1; A a2; A a3(a2); cout << A::GetCount() << endl;//可以通过类域来找 —— 突破类域 cout << a1.GetCount() << endl;//也可以通过对象来找 }
【问题】:
静态成员函数可以调用非静态成员函数吗? 不行❌,没有this
非静态成员函数可以调用类的静态成员函数吗? 可以,属于整个类
三.、友元
友元提供了一种突破封装的方式,有时提供了便利。但是友元就像会增加耦合度,破坏了封装,所以友元不宜多用。
查阅 Reference可知,cout和cin对于内置类型之所以能“自动识别类型”,是因为库里面已经把函数重载都写好了
运算符重载:让自定义类型对象可以用运算符,转换成调用这个重载函数
函数重载:支持函数名相同的函数同时存在
二者没有必然联系,好比张伟和大张伟不是同一个人一样