目录
初始化列表
explicit关键字
Static成员
友元
友元函数
友元类
匿名对象
内部类
初始化列表
初始化列表是以冒号开始,以逗号分割的成员列表,每一个成员变量后面跟一个放在括号中的初始值或表达式。(代码演示以日期类为例)
初始化列表中成员变量括号里是初始值:
class Date { public: //构造函数 Date() :_year(2001) ,_month(10) ,_day(3) { } private: int _year; int _month; int _day; }; int main() { Date d2; return 0; }
初始化列表中成员变量括号里是表达式:
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 d3(2023,1,5); return 0; }
在来回顾下学习初始化列表前日期类构造函数写法:
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 d3(2023, 1, 5); return 0; }
不管是否使用初始化列表,构造函数调用后,对象都会有一个初始值。但是对于未使用初始化列表的写法而言,构造函数函数体中的语句不能称为初始化,而是赋初值!
●因为初始化只能一次,所以每个成员变量在初始化列表中只能出现一次。
class Date { public: //构造函数 Date(int year,int month,int day) : _year(year) , _month(month) , _day(day) , _day(day) { } private: int _year; int _month; int _day; };
对于日期类而言,所有的成员变量都是内置类型。对于有自定义成员的场景,会调用它的默认构造。
●当自定义类型有默认构造时当然没有问题,但是自定义类型没有默认构造时:
//自定义类型 class A { public: //构造函数(不是默认构造) A(int a) :_a(a) { } private: int _a; }; class B { public: B(int a,int b) :_b(a) { } private: int _b; A _c; }; int main() { return 0; }
这种情况如果使用了初始化列表:
//自定义类型 class A { public: //构造函数(不是默认构造) A(int a) :_a(a) { } private: int _a; }; class B { public: B(int a,int b) :_b(a) ,_c(b) { } private: int _b; A _c; }; int main() { B b1(1,2); return 0; }
当有默认构造,且使用了初始化列表:
//自定义类型 class A { public: //默认构造函数 A(int a = 100) :_a(a) { } private: int _a; }; class B { public: B(int a,int b) :_b(a) ,_c(b) { } private: int _b; A _c; }; int main() { B b1(1,2); return 0; }
小总结:对于自定义类型成员变量,一定会先使用初始化列表初始化。
●引用成员变量
引用在变量定义时初始化,在初始化列表位置。
class B { public: B(int ref) {} private: int& _ref; }; int main() { B b3(1); return 0; }
正确写法:
class B { public: B(int ref) :_ref(ref) {} private: int& _ref; }; int main() { B b3(1); return 0; }
●const成员变量
我们知道,const修饰的变量在定义的时候一定要给初值!
int main() { const int N; return 0; }
在类中const成员变量是声明,所以没有报错。
class A { private: const int A; };
对象每个成员什么时候定义初始化呢?
答:初始化列表。
class B { public: B() :_n(10) { } private: const int _n; }; int main() { B bb; return 0; }
●成员变量在类中声明次序就是其在初始化列表中的初始化顺序 。
class C { public: C(int c) :_c2(c) , _c1(_c2) {} void Print() { cout << _c1 << " " << _c2 << endl; } private: int _c1; int _c2; }; int main() { C c1(2023); c1.Print(); return 0; }
分析:初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。在上述代码中_c1先声明,所以先初始化,此时_c2是随机值。后初始化_c2。
总结:
●对象的每个成员在初始化列表定义初始化。
●每个成员都要走初始化列表,就算不显示在初始化列表写,也会走。
●如果在初始化列表显示写了就用显示写的初始化。
●如果没有在初始化列表显示初始化:
1、内置类型,有缺省值用缺省值,没有就用随机值。
2、自定义类型,调用默认它的默认构造函数,如果没有默认构造就报错。
explicit关键字
背景:构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。
int main() { int a = 10; double b = a; return 0; }
class Date { public: Date(int year) :_year(year) {} Date& operator=(const Date& d) { if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this; } private: int _year; int _month; int _day; }; int main() { Date d1(2001); // 用一个整形变量给日期类型对象赋值 // 实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值 d1 = 2023; return 0; }
用 explicit 修饰构造函数,将会禁止构造函数的隐式转换 。explicit Date(int year) :_year(year) {}
Static成员
static用来修饰变量和函数:
■修饰局部变量,更改了局部变量的生命周期,出了作用域依然未被销毁,直到程序结束,生命周期才结束。
■修饰全局变量,使得这个全局变量只能在当前源文件下使用。出了当前文件就没法访问。未使用static修饰,全局变量的作用域是整个工程,其他源文件也可以使用。
■修饰函数,改变函数的的链接属性,只能在当前源文件下使用。
static修饰类中的成员变量和成员函数:
★static修饰的成员变量,称之为静态成员变量。
1.静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区。
class A { private: static int a; int b = 1; int c = 2; }; int main() { A aa; return 0; }
2. 静态成员变量必须在类外定义。
class A { public: A(int b = 1,int c = 2) :_a(1) { } private: static int _a; int _b; int _c; }; int main() { A aa; return 0; }
正确定义:class A { public: A(int b = 1,int c = 2) { } private: static int _a; int _b; int _c; }; int A::_a = 10; int main() { A aa; return 0; }
3.静态成员也是类的成员,受 访问限定符的限制。class A { public: A(int b = 1,int c = 2) { } private: static int _a; int _b; int _c; }; int A::_a = 10; int main() { A aa; cout << A::_a; cout << aa._a; return 0; }
class A { public: A(int b = 1,int c = 2) { } static int _a; int _b; int _c; }; int A::_a = 10; int main() { A aa; cout << A::_a<<endl; cout << aa._a<<endl; return 0; }
练习例题:实现一个类,计算程序中创建出了多少个类对象。
class A { public: A() { cout << "默认构造" << endl; ++_N; } private: static int _N; }; int A::_N = 0; int main() { A a1, a2; A a3; A a4; return 0; }
问题:无法访问私有成员,获取_N;
解决方法:写一个静态成员函数,获取_N。
★static修饰的成员函数,称之为静态成员函数。
class A { public: A() { cout << "默认构造"<<endl; ++_N; } A(const A & t) { cout << "拷贝构造"<<endl; ++_N; } ~A() { cout << "析构" << endl; --_N; } static int GetACount() { return _N; } private: static int _N; }; int A::_N = 0; int main() { cout << A::GetACount() << endl; A a1, a2; A a3(a1); cout << A::GetACount() << endl; return 0; }
调用析构函数之前:
调用析构函数后:
题目链接:求1+2+3+...+n_牛客题霸_牛客网
静态成员函数没有隐藏的this指针,不能访问任何非静态成员。
class A { public: A() { cout << "默认构造" << endl; ++_N; } static int Get_N() { _a = 10; return _N; } private: static int _N; int _a; }; int A::_N = 0; int main() { A a1, a2; return 0; }
友元
友元函数
场景:在类外定义的函数想要访问类里的成员!此时就可以用友元来解决。(以日期类为例)
class Date { public: //构造函数 Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //拷贝构造 //析构函数 void Print() { cout << _year << "年" << _month << "月" << _day << "日"; } private: int _year; int _month; int _day; }; void Fun() { Date d1; d1._year = 1314; d1.Print(); } int main() { Fun(); return 0; }
此时只用友元解决问题:在类的内部加上类外函数的声明, 声明前加上friend关键字。(友元声明可以在类定义的任何地方声明,不受类访问限定符限制)。
class Date { friend void Fun(); public: //构造函数 Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //拷贝构造 //析构函数 void Print() { cout << _year << "年" << _month << "月" << _day << "日"; } private: int _year; int _month; int _day; }; void Fun() { Date d1; d1._year = 1314; d1.Print(); } int main() { Fun(); return 0; }
例子2:<<和>>重载,不同于其他运算符重载,这两个函数一般写在类外,用友元声明访问类中的成员。因为如果写成类中的成员函数,this指针默认抢占第一个参数的位置,Date对象就是左操作数,我们使用起来就会非常不习惯!
例:
d1>>cout;
针对这个问题,练习友元的使用。当然在实践之前先对ostream和istream两个类简单的了解下:
cin是istream类型的对象,cout是ostream类型的对象!简单的说就是库中实现了ostream和istream两个类,cout和cin是实例化出的对象。它们可以很好的处理内置类型的流插入和流提取,这也是在初始c++中谈到的自动识别类型的问题。
但是针对日期类这样的自定义类型是不能搞定的,需要用户显示写。
class Date { friend ostream& operator<<(ostream& _cout, const Date& d); friend istream& operator>>(istream& _cin, Date& d); public: Date(int year = 1, int month = 1, int day = 1) : _year(year) , _month(month) , _day(day) {} private: int _year; int _month; int _day; }; //流插入重载 ostream& operator<<(ostream& _cout, const Date& d) { _cout << d._year << "年" << d._month << "月" << d._day<<"日"; return _cout; } //流提取重载 istream& operator>>(istream& _cin, Date& d) { _cin >> d._year; _cin >> d._month; _cin >> d._day; return _cin; } int main() { Date d; cin >> d; cout << d << endl; return 0; }
小总结:
▲友元函数可访问类的私有和保护成员,但不是类的成员函数。▲友元函数不能用const修饰。▲友元函数可以在类定义的任何地方声明,不受类访问限定符限制。▲一个函数可以是多个类的友元函数。▲友元函数的调用与普通函数的调用原理相同。
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。语法:class A { friend class B; private: int _a1; int _a2; }; class B { public: void Fun() { _aa._a1 = 10; _aa._a2 = 100; } private: int _b1 = 1; int _b2 = 1; A _aa; }; int main() { B bb; bb.Fun(); return 0; }
需要注意的是,友元的关系是单向的(你把小红当做最好的朋友,在她面前没有秘密,但是从小红的角度看,你只是她最普通不过的一个同学,仅此而已),例如上述代码,A类中有B类的友元声明,所以在B类中可以访问A类的私有成员变量,但是反过来A类在没有声明友元的情况下访问B类的私有成员变量是不可以的! 同时:友元关系是不可传递的,这个也很好理解,小红和小刚是朋友,和小美是朋友,那么小刚和小美一定是朋友吗?答案是否定的。
匿名对象
匿名对象的特点就是不取名字。
//取名 Date d1; d1.Fun(); //匿名 Date().Fun();
▲匿名对象的声明周期只有一行。
class A { public: A(int a = 0) :_a(a) { cout << "A(int a)" << endl; } ~A() { cout << "~A()" << endl; } private: int _a; }; int main() { A(); return 0; }
调试结果:对象创建的下一行调用析构函数,说明它的声明周期只有一行。
内部类
定义:在一个类中定义了另一个类
class A { public: //构造 A() {} //析构 ~A() {} class B { public: void Fun() { A aa; aa._a1 = 10; aa._a2 = 100; } private: int _b1 = 2; int _b2 = 2; }; private: int _a1 = 1; int _a2 = 1; }; int main() { A::B b; b.Fun(); return 0; }
内部类是一个独立的类, 它不属于外部类,更不能通过外部类的对象去访问内部类的成员。分析:
★内部类是外部类的友元类,但是外部类不是内部类的友元。
★内部类可以定义在外部类中的任意位置。
★内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
问题:A的大小是多少。算不算内部类?
class A { public: class B { private: int _b1 = 2; int _b2 = 2; }; private: int _a1 = 1; int _a2 = 1; }; int main() { A a; cout << sizeof(a); return 0; }
答:sizeof(外部类)=外部类,和内部类没有任何关系。