目录
章节架构:
一.类的构造函数的初始化列表
1.类的构造函数初始化列表的引入和介绍
- 当一个类对象被创建时,其成员变量的定义(成员变量内存空间的开辟和初始化)是在构造函数函数体被执行之前完成的:
比如:
class Date { public: Date(int year = 0,int day=0) Date的构造函数 { cout << "pause" << endl; _year = year; _day = day; } private: int _year; int _day; }; int main() { Date a; return 0; }
- 因此构造函数体中的语句只是为成员变量赋值,而不是成员变量的初始化(变量的初始化指的是为变量申请内存空间后立马为其赋初值)(初始化在变量的生命周期中只能进行一次,而构造函数体内可以多次赋值)
- 如果类中存在const成员变量(只可读,不可写入的变量),构造函数是无法为其初始化的。
比如:
代码段无法通过编译。
C++为了解决这个问题,设计了构造函数的初始化列表,用来进行成员变量的初始化(为成员变量开辟内存空间后立马赋予其初值)。
- 初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
比如:
class Date { public: Date(int year = 0, int month = 0, int day = 0) : _year(year) 成员变量的初始化列表 , _month(month) , _day(day) , _Cday(3) const成员变量的初始化 { 构造函数函数体 cout << "pause" << endl; } private: int _year; int _month; int _day; const int _Cday; }; int main() { Date a; return 0; }
![]()
2.初始化列表用于类的类对象成员的拷贝构造函数的调用
关于类的拷贝构造函数:http://t.csdn.cn/HNeit
有些编译器(比如vs系列)不会为类的自定义拷贝构造函数添加调用其类对象成员变量的拷贝构造函数的指令,比如:#include <iostream> using std::cout; using std::cin; using std::endl; class subdate { public: subdate() 类的构造函数 :_a(0) { cout << "subconstructor" << endl; } subdate(subdate& date) 类的拷贝构造函数 { cout << "copysubdate" << endl; } private: int _a; }; class Date { public: Date(int year = 0, int month = 0, int day = 0) 类的构造函数 : _year(year) , _month(month) , _day(day) , _Cday(3) ,_test() { cout << "constructor" << endl; } Date(Date & date) 类的自定义拷贝构造函数 : _Cday(3) { cout << "Dateconstructor" << endl; } private: int _year; int _month; int _day; const int _Cday; subdate _test; }; int main() { Date a; Date b(a); 对象b的创建会调用拷贝Date的自定义拷贝构造函数 return 0; }
图解分析:
若想让类的自定义拷贝构造函数可以去调用该类的类对象成员的拷贝构造函数,我们就必须在拷贝构造函数的初始化列表中显式地表明类对象成员的拷贝构造函数的调用。
即对Date类的自定义拷贝构造函数作如下修改:
Date(Date & date) //类的自定义拷贝构造函数 : _Cday(3) ,_test(date._test) { cout << "Dateconstructor" << endl; }
3.初始化列表的使用细则
- 每个成员变量在初始化列表中只能出现一次(因为初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 类对象成员变量(尤其是该类没有可以无参调用的构造函数时)
比如:
class A { public: A(int a) 该构造函数不可无参调用 :_a(a) {} private: int _a; }; class B { public: B(int a, int ref) 必须将其成员置于初始化列表中 :_aobj(a) ,_ref(ref) ,_n(10) {} private: A _aobj; 没有可以无参调用的构造函数 int& _ref; 引用成员 const int _n; const成员变量 };
初始化列表可以让系统在为类成员变量开辟内存空间的同时为成员变量赋初值,并且还可以让我们显式地在自定义构造函数中调用类中类对象成员的构造函数。
因此,无论何种情况下,我们都应该尽量在类的构造函数中使用初始化列表初始化成员变量
(成员变量的初始化顺序最好和成员变量的声明顺序保持一致)
4.使用初始化列表的一个注意事项
类的成员变量在类的定义中的声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的书写先后次序无关。
比如:class A { public: A(int a=1) :_a1(a) , _a2(_a1) {} void Print() { cout << _a1 << " " << _a2 << endl; } private: int _a2; int _a1; }; int main() { A aa(1); aa.Print(); return 0; }
所以代码段中aa对象被创建时,_a2会被赋予随机值(_a2会先完成初始化,_a2初始化时_a1空间中存放的还是随机值)
二.explicit关键字
C++中存在如下的类对象创建方式:
class Date { public: Date(int year = 0) 构造函数 { _year = year; cout << "constructor"<<endl; } Date(Date& date) 拷贝构造函数 { _year = date._year; cout << "copy"<<endl; } private: int _year; }; int main() { Date a = 2022; 该代码语句发生了隐式类型转换 return 0; }
代码段中发生了隐式类型转换:
然而这种书写方式容易引起误解,因此可以在类的构造函数前加上explicit关键字来禁止这种隐式类型转换的发生。
三.C++类的static成员
1.类中static修饰的成员变量
static修饰的成员变量(静态成员变量)的特性:
- 静态成员变量的生命周期与全局变量相同(程序启动时创建,结束时销毁)
- 静态成员变量不占用任何类对象的内存空间(其存在不依赖于任何对象),它被独立地存放在静态区,所有该类的类对象都可以访问该静态成员变量
- 静态成员变量必须在类外定义,定义时不添加static关键字但是要表明它属于哪个类域.
- 静态成员变量可用 类名::静态成员 或者 对象.静态成员 来访问
比如:
2.类中static修饰的成员函数
static修饰的成员函数(静态成员函数)的特性:
- 类静态成员函数可用 类名::静态成员函数 或者 对象.静态成员函数 来访问
- 静态成员函数的形参中没有隐藏的this指针,不能直接访问任何非静态成员变量
- 静态成员函数不可以直接调用非静态成员函数
- 非静态成员函数可以直接调用类的静态成员函数
比如:
3.相关练习
牛客网JZ64 求1+2+3+...+n
描述
求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
数据范围: 0<n≤200
进阶: 空间复杂度 O(1) ,时间复杂度 O(n)
可以借助类的静态成员来解决这个问题:
class Ans { public: Ans() 每调用一次构造函数就累加一次 { i++; 两个静态变量的生命周期为全局变量的生命周期 ret+=i; } int Getans () { return ret; } private: static int i; static int ret; }; int Ans::i =0; int Ans:: ret =0; class Solution { public: int Sum_Solution(int n) { Ans* ptr = new Ans[n-1]; Ans ans; 一共创建了n个Ans对象,调用了n次Ans的构造函数 return ans.Getans(); } };
四.类的友元函数和友元类
1.类的友元函数
类的友元函数的概念:
友元函数是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
- 友元函数可直接访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
比如现在我们在类外来重载两个运算符:
#include <iostream> using std::endl; using std::istream; using std::ostream; class Date { public: friend void operator>>(istream& cin, Date& date); 在类中声明类的友元函数 friend void operator<<(ostream& cout, Date& date); private: int _year; }; void operator>>(istream& cin, Date& date) 重载>>运算符 { cin >> date._year; std::cout << "operator>>" << endl; } void operator<<(ostream& cout, Date& date) 重载<<运算符 { cout << date._year << endl; std::cout << "operator<<" << endl; } int main() { Date a; std::cin >> a; 调用重载的>>运算符 std::cout << a; 调用重载的<<运算符 return 0; }
2.类的友元类
class Time { friend class Date; 声明Date类为时间类的友元类,则在Date类中就可以直接访问Time类 中的私有成员变量 public: private: int _hour; int _minute; int _second; }; class Date { public: void SetTimeOfDate(int hour, int minute, int second) { _t._hour = hour; 直接访问时间类私有的成员变量 _t._minute = minute; _t._second = second; } private: Time _t; };
类的友元类的基本概念:
- 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
- 友元关系不能传递
如果C是B的友元, B是A的友元,则不能说明C时A的友元
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元能不用就不用。
五.拷贝对象时的一些编译器优化
给出一段代码:
class A { public: A(int a = 0) A的构造函数 :_a(a) { cout << "A(int a)" << endl; } A(const A& aa) A的拷贝构造函数 :_a(aa._a) { cout << "A(const A& aa)" << endl; } A& operator=(const A& aa) A的赋值运算符重载 { cout << "A& operator=(const A& aa)" << endl; if (this != &aa) { _a = aa._a; } return *this; } ~A() A的析构函数 { cout << "~A()" << endl; } private: int _a; }; void f1(A aa) { ; } A f2() { A aa; return aa; } int main() { A a = f2(); cout << endl; return 0; }