本节继续进行类和对象的介绍,再坚持坚持,前方就是休息室了!
我们知道,类对象定义时会自动调用类的构造函数完成对类对象成员变量的初始化。 前文我们并没有对构造函数进行进一步的探讨,即类对象创建时类成员变量具体是在构造函数哪里初始化的? 其实,构造函数内对成员变量赋值的操作并不能称之为对成员变量的初始化,而是只能称之为对成员变量赋初值。
class Date {
public:
//默认 构造
Date(int year = 1, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
那么成员变量的定义发生在构造函数的哪里呢? 这需要引入构造函数的初始化列表的概念了:
以一个冒号
:
开始,接着是一个以逗号,
分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式;: _member1(expression1), _member(expression2), ...
每个构造函数都有一个初始化列表用于对每个成员变量进行初始化; 初始化列表默认是由编译器隐式生成的,如果一个成员变量被我们显式的在初始化列表中写了,那么编译器就不在初始化列表中生成该成员变量的默认初始化了; 每一个类对象的成员变量的定义都发生在构造函数的初始化列表中; 在调用构造函数时,先进行初始化列表中的操作,在进行构造函数体内的操作;
class Date {
public:
//默认 构造
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day){
}
private:
int _year;
int _month;
int _day;
};
对于隐式的(默认的)初始化列表: 对于内置类型,隐式初始化列表会把内置类型初始化为随机值或0值,具体是哪一种与具体的编译器有关,C++标准也没有对此进行规定; 对于自定义类型,隐式初始化列表会直接调用自定义类型变量的自己的默认构造函数进行初始化;
class A {
public:
A(int a = 0) {
cout << "构造函数: A(int a = 0)" << endl;
_a = a;
}
private:
int _a;
};
class B {
public:
B(int b = 1) :_b(b){
cout << "构造函数: B(int b = 1)" << endl;
}
private:
int _b;
A _aa;
};
int main() {
B b1(10);
return 0;
}
每个成员变量在初始化列表中只能出现一次 即每个成员变量初始化只能初始化一次
class A {
public:
A(int a = 0)
:_a(a)
,_a(a){//error
}
private:
int _a;
};
int main() {
A a1;
return 0;
}
类中有引用成员变量、const成员变量
引用成员变量必须在定义时初始化,const修饰的成员变量也必须在定义时初始化,二者都只能初始化一次; 类成员变量又是在构造函数初始化列表进行初始化的,故引用成员变量和const修饰成员变量都必须在初始化列表进行显式初始化; const成员变量错误举例:
const成员变量正确举例:
引用成员变量错误举例:
引用成员变量正确举例:
类中有自定义类型成员,且该类没有默认构造函数 因为在创建类对象时先调用构造函数,在构造函数的初始化列表会自动调用自定义类行变量的默认构造函数,如果该自定义类型没有默认构造函数程序就会报错;
class A {
public:
A(int a)
:_a(a){
}
private:
int _a;
};
class B {
public:
B(int b = 1) :_b(b) {
;
}
private:
int _b;
A _aa;
};
int main() {
B b1;
return 0;
}
class A {
public:
A(int a)
:_a(a){
}
private:
int _a;
};
class B {
public:
/*B(int b = 1) :_b(b) {
;
}*/
private:
int _b;
A _aa;
};
int main() {
B b1;
return 0;
}
定义B类的对象时会调用B的默认构造,在B的默认构造初始化列表会调用A的默认构造, 但A没有默认构造,所以报错,但报的是没有B的默认构造函数; 不定义B的对象并不会报错,因为没有调用B的默认构造;
我们需要对没有默认构造函数的自定义类型成员变量显式的在构造函数的初始化列表中进行初始化
class A {
public:
A(int a)
:_a(a){
}
private:
int _a;
};
class B {
public:
B(int b = 1, int a = 10) :_b(b) ,_aa(a){
;
}
private:
int _b;
A _aa;
};
int main() {
B b1;
return 0;
}
因为不管是否使用初始化列表,成员变量都会先通过初始化列表进行初始化; 初始化列表并不能解决所有问题,初始化列表与构造函数内赋初值常常会混合使用; 比如初始时动态申请空间时:
class Stack {
public:
//普通构造
Stack(int capacity = 4)
:_top(0)
,_capacity(capacity){
_array = (int*)malloc(sizeof(int) * capacity);
if (_array == nullptr) {
perror("Stack(int capacity = 4) malloc file");
exit(-1);
}
}
~Stack() {
free(_array);
_array = nullptr;
_top = _capacity = 0;
}
private:
int* _array;
size_t _top;
size_t _capacity;
};
int main() {
Stack st;
return 0;
}
初始化列表中 变量实际定义初始化 的顺序与变量的声明顺序相同,也就是说变量声明的顺序决定了变量初始化列表初始化的顺序,而与初始化列表中变量书写的顺序无关 这可能与编译器的底层实现有关
class A {
public:
A(int a = 1)
:_b(a)
,_a(_b) {
}
private:
int _a;
int _b;
};
int main() {
A a1;
return 0;
}
因为全缺省的默认构造函数功能十分强大,十分好用; 我们在创建类对象时既可以不传参数,完全使用缺省参数,也可以传一部分参数,使用部分缺省参数,也可以传全部参数,不使用缺省参数;
class A {
public:
A(int a = 1, int b = 2, int c = 3)
:_a(a)
,_b(b)
,_c(c){
}
private:
int _a;
int _b;
int _c;
};
int main() {
A a1;
A a2(10);
A a3(10, 20);
A a4(10, 20, 30);
return 0;
}
构造函数核心–初始化列表 构造函数完成初始化的功能可以分为两部分:
1.初始化列表进行的 定义初始化 2.构造函数体内的对初始化列表已定义变量的初始化,或者说赋值更好
每个类成员变量默认(隐式的)都会经过初始化列表, 我们也可以显式的在初始化列表中写, 也就是说,不管我们在初始化列表中是否显式对类成员变量进行定义初始化,成员变量都会在初始化列表被定义初始化
类对象定义时,类成员变量整体 定义初始化 细化类成员变量的 定义初始化: 类成员变量的 定义初始化 发生在初始化列表阶段 即类成员变量在初始化列表进行 定义初始化 而在构造函数内进行的是类成员变量的赋值和其他必要的操作,故在构造函数内进行的不能称之为 定义初始化 ,只能叫做赋初值 毕竟一个类成员变量在生命周期内只能定义一次
对于普通变量来说, 在不在初始化列表显式定义初始化 都可以,因为普通变量 定义初始化 之后还可以在构造函数内修改
对于定义之后不可修改的变量(const修饰的、引用的): 我们必须显式的在初始化列表进行定义初始化,而不能在构造函数体内进行初始化,在函数体内称为赋值也许更好理解 因为所有变量都已经在初始化列表定义初始化过了
对于自定义类型,初始化列表时会调用该类型的默认构造函数, 如果该类没有默认构造函数我们就需要在初始化列表手动对该自定义类型进行定义初始化,不然就会报错(错误是:找不到该自定义类型的默认构造函数)
explicit
关键字修饰的构造函数不能发生隐式类型转换
我们知道在不同类型变量进行赋值操作时会发生隐式类型转换,对于类类型也是如此;
double d = 3.14;
int a = d;
浮点型变量d赋值给整型变量a发生隐式类型转换: 首先生成一个匿名整型临时变量
假设叫tmp
,这个临时变量的值是浮点型变量d
的整数部分即3
,临时变量再赋值给整型变量a
;
int a = 3.14;
浮点型字面值
3.14
赋值给整型变量a
发生隐式类型转换: 首先生成一个匿名临时变量假设叫tmp
,这个临时变量的是浮点型字面值的整数部分即3
,临时变量再赋值给整型变量a
;
double d = 3.14;
const int& rd = d;
整型引用变量
rd
引用浮点型变量d
发生隐式类型转换: 首先生成一个匿名临时整型变量,这个匿名临时变量的值是浮点型变量d
的整数部分,整型引用变量引用的是这个匿名临时引用变量;同时匿名临时变量具有常性,所以整型引用变量rd
需要const
修饰;
class A {
public:
A(int a)
:_a(a) {
cout << "构造: A(int a)" << endl;
}
A(A& a) {
cout << "拷贝构造: A(A& a)" << endl;
_a = a._a;
}
private:
int _a;
};
int main() {
A a1(10);//构造
A a4 = a1;//拷贝构造
return 0;
}
int main() {
A a2 = 10;//隐式类型转换 10 --> 匿名临时A类对象 --> a2;构造+拷贝构造
//等价于 const A tmp(10); A a2(tmp);
return 0;
}
这里是整型字面值赋值给类对象a2,发生隐式类型转换: 先生成一个匿名临时A类对象
调用构造函数
,在用这个临时变量初始化类对象调用拷贝构造
;
int main() {
const A& a3 = 100;//隐式类型转换 ;引用的不是整型100,引用的是临时匿名A类对象
//等价于 const A tmp(100); const A a2(tmp);
return 0;
}
整型字面值100被类对象
a3
引用,发生隐式类型转换: 首先创建一个匿名临时类对象调用构造函数
,类类型引用变量a3
引用这个匿名临时类对象
刚才我们分析了创建类对象或类对象参与的赋值操作时隐式类型转换是如何发生的; 我么可以看到隐式类型转换的发生会创建临时变量,这其实是额外的开销,如今的编译器大多对其进行了优化;
int main() {
A a2 = 10;//隐式类型转换 10 --> 匿名临时A类对象 --> a2;构造+拷贝构造
//等价于 const A tmp(10); A a2(tmp);
return 0;
}
本来隐式类型转换产生了临时类对象,然后临时类对象在拷贝初始化
a2
; 这里涉及到产生临时类对象的拷贝构造
,拷贝初始化时的拷贝构造
编译器呢如今可以直接将这两个步骤优化为一个步骤:直接构造类对象a2
; 相当于A a2 = 10; ----> A a2(10);
只调用一次拷贝构造即可完成类对象a2
的初始化;
编译器是对连续步骤中的优化,对于分开的步骤,考虑到创建的类对象可能还有其他用途,编译器并不会对分开的步骤进行优化;
int main() {
A tmp(100);//构造函数
A a2(tmp);//拷贝构造
return 0;
}
这里是显式分开的步骤创建类对象
a2
: 先创建类对象tmp
,调用构造函数; 再创建类对象a2
,调用拷贝构造 编译器无法把拷贝构造这一步优化掉,因为类对象tmp
可能会在程序后面使用;
使用explicit修饰的单参数构造函数不能发生隐式类型转换了; 于是一些操作就被禁止了;
class A {
public:
explicit A(int a)
:_a(a) {
cout << "构造: A(int a)" << endl;
}
A(A& a) {
cout << "拷贝构造: A(A& a)" << endl;
_a = a._a;
}
private:
int _a;
};
int main() {
A a2 = 10;//隐式类型转换 10 --> 匿名临时A类对象 --> a2;构造+拷贝构造
//等价于 const A tmp(10); A a2(tmp);
return 0;
}
const A& a3 = 100;//隐式类型转换 ;引用的不是整型100,引用的是临时匿名A类对象
//等价于 const A tmp(100); const A& a2 = tmp;
C++11中支持了多参构造函数的隐式类型转换
class A {
public:
A(int a = 1,int b = 1, int c = 1)
:_a(a)
,_b(b)
,_c(c){
cout << "构造: A(int a = 1,int b = 1, int c = 1)" << endl;
}
A(A& a) {
cout << "拷贝构造: A(A& a)" << endl;
_a = a._a;
_b = a._b;
_c = a._c;
}
private:
int _a;
int _b;
int _c;
};
int main() {
A a1(10, 20, 30);//构造
return 0;
}
类对象创建时调用的普通构造函数;
int main() {
A a2 = { 1,2,3 };//构造+拷贝构造 --> 优化为 构造
return 0;
}
这里花括号内的三个整型字面值为类对象
a2
赋值,发生隐式类型转换: 首先花括号内三个整形字面值作为参数创建匿名临时类对象并调用构造函数
,然后这个临时类对象再拷贝赋值给类对象a2
,并调用拷贝构造函数
;
int main() {
const A& a3 = { 1,2,3 };//构造
return 0;
}
这里花括号里三个整形字面值被类类型引用变量
a3
引用时,发生隐式类型转换: 首先花括号内三个整形字面值作为参数创建匿名临时类对象并调用构造函数
,然后类类型引用变量a3
再引用这个临时变量; 并且由于临时对象具有常性const
,多以类类型引用变量需要用const
修饰;
编译器是对连续步骤中的优化,对于分开的步骤,考虑到创建的类对象可能还有其他用途,编译器并不会对分开的步骤进行优化;
int main() {
A a2 = { 1,2,3 };//构造+拷贝构造 --> 优化为 构造
return 0;
}
使用explicit
修饰的单参数构造函数不能发生隐式类型转换了;
于是一些操作就被禁止了,这对于多参数的构造函数来说同样适用;
class A {
public:
explicit A(int a = 1, int b = 1, int c = 1)
:_a(a)
, _b(b)
, _c(c) {
cout << "构造: A(int a = 1,int b = 1, int c = 1)" << endl;
}
A(A& a) {
cout << "拷贝构造: A(A& a)" << endl;
_a = a._a;
_b = a._b;
_c = a._c;
}
private:
int _a;
int _b;
int _c;
};
int main() {
A a2 = { 1,2,3 };//构造+拷贝构造 --> 优化为 构造
return 0;
}
int main() {
const A& a3 = { 1,2,3 };//构造
return 0;
}
声明为static
的类成员称为类的静态成员;
用static
修饰的成员变量,称之为静态成员变量;
用static
修饰的成员函数,称之为静态成员函数;
静态成员变量一定要在类外进行定义初始化 ; 静态成员变量的定义初始化时需要指定作用域为类的作用域,以此来区分全局静态变量; 静态成员变量并不在类中,即不是每个类对象中都有一份静态成员变量,而是在程序运行时就在静态区创建,整个程序运行期间只有一份; 静态成员函数不在类中,也不在公共代码段,而是和静态成员变量一样也在静态区;
class A {
public:
A(int a = 1):_a(a){}
private:
int _a;
static int _b;
};
int A::_b = 0;
int main() {
A aa;
int size1 = sizeof(A);
int size2 = sizeof(aa);
return 0;
}
静态的成员变量 不属于任何一个对象,在静态区只有一份,任何一个对象都能访问到;
下面的只是说明静态成员变量_b
是在类A域
中的_b
,起作用的是类型,而不是对象本身,这与成员函数非常相似;
class A {
public:
A(int a)
:_a(a) {
}
static int _b;//声明
private:
int _a;
};
int A::_b = 0;//定义初始化
int main() {
A a1(1);//构造
cout << A::_b << endl;
cout << a1._b << endl;
A* ptr = &a1;
cout << ptr->_b << endl;
ptr = nullptr;
cout << ptr->_b << endl;
return 0;
}
静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区 静态成员变量在类中用
static
修饰进行声明,在类外进行定义初始化,并且在类外不加static修饰,需要指定类域;
class A {
public:
A(int a = 1):_a(a){}
private:
int _a;
static int _b;
};
int A::_b = 0;
类静态成员即可用
类名::静态成员
或者对象.静态成员
来访问
class A {
public:
A(int a = 1):_a(a){}
//private:
int _a;
static int _b;
};
int A::_b = 0;
int main() {
A aa;
cout << A::_b << endl;
cout << aa._b << endl;
return 0;
}
静态成员也是类的成员,受
public
、protected
、private
访问限定符的限制静态成员函数没有隐藏的
this指针
,不能访问任何非静态成员 我们知道,普通的类成员函数第一个形参都是隐式的this指针
,而静态成员函数形参没有this指针
; 而this指针
指向的是类对象,故静态成员函数无法访问类对象的普通成员变量,无法调用普通成员函数; 只能访问静态成员变量和调用静态成员变量;
class A {
public:
A(int a = 1) :_a(a) {}
//静态成员函数
static void Function1() {
cout << _b << endl;
}
//非静态成员函数
void Function2() {
cout << _a << " " << _b << endl;
}
private:
int _a;
static int _b;
};
int A::_b = 0;
int main() {
A aa;
aa.Function1();
A::Function1();
aa.Function2();
//A::Function2();//error
return 0;
}
类对象的创建会调用构造函数,要么是直接构造,要么是拷贝构造; 我们显式的写出构造函数和拷贝构造函数
int Count = 0;
class A {
public:
A(int a)
:_a(a) {
Count++;
}
A(A& a) {
Count++;
_a = a._a;
}
private:
int _a;
};
int main() {
A a1(1);//构造
cout << Count << endl;//Count == 1
A a2 = 1;//构造+拷贝构造 --> 直接构造
cout << Count << endl;//Count == 2
const A& a3 = 1;//构造
cout << Count << endl;//Count == 3
return 0;
}
static
成员变量在显式的构造函数和拷贝构造函数内计数:class A {
public:
A(int a)
:_a(a) {
Count++;
}
A(A& a) {
_a = a._a;
Count++;
}
static int Count;//声明
private:
int _a;
};
int A::Count = 0;//定义初始化
int main() {
A a1(1);//构造
cout << A::Count << endl;//Count == 1
A a2 = 1;//构造+拷贝构造 --> 直接构造
cout << A::Count << endl;//Count == 2
const A& a3 = 1;//构造
cout << A::Count << endl;//Count == 3
return 0;
}
//传值传参
void Func1(A a) {
;
}
int main() {
A a1(1);//构造
cout << A::Count << endl;//Count == 1
Func1(a1);
cout << A::Count << endl;//Count == 2
return 0;
}
//传值返回
int main() {
A a1(1);//构造
cout << A::Count << endl;//Count == 1
Func2();
cout << A::Count << endl;//Count == 3
return 0;
}
//传引用传参
class A {
public:
A(int a) :_a(a) {
Count++;
}
A(A& a) {
_a = a._a;
Count++;
}
static int Count;//声明
private:
int _a;
};
int A::Count = 0;//定义初始化
//传引用传参
void Func3(A& a) {
;
}
int main() {
A a1(1);//构造
cout << A::Count << endl;//Count == 1
Func3(a1);
cout << A::Count << endl;//Count == 1
return 0;
}
//传引用返回
A& Func5() {
static A a(1);
return a;
}
int main() {
Func5();
cout << A::Count << endl;//Count == 1
return 0;
}
static A a(1);
//传引用返回
A& Func5() {
return a;
}
int main() {
cout << A::Count << endl;//Count == 1
Func5();
cout << A::Count << endl;//Count == 1
return 0;
}
一般类中的成员变量我们在类外是不能直接访问的,静态成员变量也是如此; 所以我们通常会将成员变量声明为私有的,我们在通过成员函数间接得到需要的成员变量的值;
class A {
public:
A(int a)
:_a(a) {
Count++;
}
A(A& a) {
Count++;
_a = a._a;
}
static int GetCount() {
return Count;
}
private:
int _a;
static int Count;//声明
};
int A::Count = 0;//定义初始化
int main() {
A a(10);
cout << A::GetCount() << endl;
return 0;
}
类对象创建时都要调用构造函数;
class A {
public:
A(int a)
:_a(a) {
}
private:
int _a;
};
类对象可以创建在栈上:A a1(10);
创建在堆区:A* a2 = new A;
创建在静态区:static A a3(10);
这三种创建方式都要调用构造函数,如果限制只能在栈上创建对象,我们可以把构造函数用private
修饰,使得外部不能够调用构造函数,然后再写一个类对象在栈上创建的函数,并返回这个类对象的拷贝即可注意拷贝构造函数并不是私有的,即拷贝构造在类外可以调用,用于类对象返回时对临时对象进行拷贝构造;
如果拷贝构造也设置成私有的,那么在类外无法调用拷贝构造,也就无法在类外进行拷贝构造,导致类对象返回无法以拷贝构造的方式创建临时类对象,也就是直接无法在类外创建对象了,这样路就全给堵死了
;
//要求对象只能在栈上创建
class A {
public:
static A GetObj(int a) {
A aa1(a);
return aa1;
}
private:
A(int a)
:_a(a) {
}
private:
int _a;
};
int main() {
/*static A a1(1);//在静态区创建类对象
A a2(1);//在栈上创建类对象
A* a3 = new A;*///在堆区创建类对象
A a4 = A::GetObj(1);
return 0;
}
static
修饰:储存在静态区
局部静态变量生命周期变为程序运行期间; 全局静态变量生命周期为程序运行期间,且影响全局变量的链接属性,使得全局变量只能在本文件中被找到,独属于本文件;
全局域:储存在静态区
全局变量声明周期程序运行期间;
局部域:储存在栈区
局部变量生命周期从进入局部域开始,到出局部域为止;
动态申请的空间:储存在堆区
生命周期从申请开始,知道申请者手动释放;
常量:储存在常量区
字面值常量,生命周期为程序运行期间;
C++引入了类的概念,类体现了C++的封装的特点,封装就是类内对类外的部分隐藏,类外无法自由自在的对类内成员进行访问和修改; 这总体来说是好的,类的隐蔽特点避免了很多不安全的隐患; 但是某些时候也确实对类成员的访问形成了限制导致很不方便,为了应对必要的类外普通函数对类内成员的访问同时尽量不破坏类的封装特点,便引入了友元的概念;
友元是针对类外的函数或另一个类来说的,分为友元函数和友元类
友元说到底还是破坏了类的封装,一般对于友元函数和友元类使用不多;
类外的普通函数,在类内任意位置写上该普通函数的声明,再声明前加上friend关键字
修饰;
例如:
class A {
public:
friend void Function(const A& a);
private:
int _a = 1;
};
void Function(const A& a) {
cout << a._a << endl;
}
int main() {
A a;
Function(a);
return 0;
}
友元函数我们在运算符重载那里已经提前提到了,对于流插入运算符<<
和流提取运算符>>
的重载必须要使用友元函数来完成;
class Date {
public:
friend ostream& operator<<(ostream& output, const Date& d);
friend istream& operator>>(istream& input, Date& d);
private:
int _year;
int _month;
int _day;
};
ostream & operator<<(ostream & output, const Date & d) {
output << d._year << "/" << d._month << "/" << d._day << endl;
return output;
}
istream& operator>>(istream& input, Date& d) {
input >> d._year >> d._month >> d._day;
return input;
}
友元函数可访问类的私有和保护成员,但不是类的成员函数;
友元函数不能只用const
修饰,类内的声明要与类外的定义匹配,即要加const
,就都加上const
;
class A {
public:
friend const void Function(const A& a);
private:
int _a = 1;
};
const void Function(const A& a) {
cout << a._a << endl;
}
int main() {
A a;
Function(a);
return 0;
}
友元函数可以在类定义的任何地方声明,不受类访问限定符限制;
一个函数可以是多个类的友元函数;
class A {
public:
friend class B;
friend void Function(const A& a, const B& b);
private:
int _a = 1;
};
class B {
public:
friend void Function(const A& a, const B& b);
private:
int _b = 1;
};
void Function(const A& a, const B& b) {
cout << a._a << endl;
cout << b._b << endl;
}
int main() {
A a;
B b;
Function(a, b);
return 0;
}
友元函数的调用与普通函数的调用原理相同 ;
友元类,即一个类可以访问另一个类的成员;
class A {
public:
friend class B;
private:
int _a = 1;
};
class B {
public:
void Function(const A& a) {
cout << a._a << endl;
cout << _b << endl;
}
private:
int _b = 2;
};
int main() {
A a;
B b;
b.Function(a);
return 0;
}
友元关系是单向的,不具有交换性
即A是B的友元,而B并不是A的友元
友元关系不具有传递性
即A是B的友元,B是C的友元,而A并不是C的友元
友元关系不能继承
定义在一个类内部的类称之为内部类,从形式上看是一个类包含者另一个类;
class A {
public:
class B {
public:
void Function(A& aa) {
cout << aa._a << endl;
cout << _b << endl;
}
private:
int _b = 2;
};
private:
int _a = 1;
};
int main() {
A a;
A::B b;
b.Function(a);
return 0;
}
先来看看外部类的大小与内部类的大小之间有没有什么关系:
sizeof(外部类)
计算的是外部类的大小,和内部类没有任何关系;
class A {
public:
class B {
public:
void Function(const A& aa) {
cout << aa._a << endl;
cout << _b << endl;
}
private:
int _b = 2;
};
private:
int _a = 1;
};
int main() {
A::B b;
cout << sizeof(A) << endl;
cout << sizeof(A::B) << endl;
return 0;
}
内部类是一个独立的类,它不属于外部类,不能通过外部类的对象去访问内部类的成员
内部类天生是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员,反之则不成立;
内部类定义在外部类的public、protected、private
时,对内部类的使用会受到访问限定符的影响,这一点与内部类外部类的其他成员相同;
public修饰
时在类外指定外部类类域即可使用内部类成员;
private修饰
时则在类外不能使用内部类任何成员;
内部类可以直接访问外部类中的static
成员,不需要外部类的对象/类名;
class A {
public:
static int count;
class B {
public:
void Function() {
cout << count << endl;
cout << _b << endl;
}
private:
int _b = 2;
};
private:
int _a = 1;
};
int A::count = 0;
int main() {
A::B b;
b.Function();
return 0;
}
匿名对象,也是对象,只是没有名字,常常用于简化步骤; 前面我们基本都在接触有名字的对象,接下来瞅瞅匿名对象的使用,认识一下;
class A {
public:
A(int a = 1)
:_a(a) {}
void Print() {
cout << _a << endl;
}
private:
int _a;
};
先给出有名对象
int main() {
A a1;
A a2(10);
A a3 = 10;
//A a4();//error
return 0;
}
生命周期只有定义的那一行,这一行结束生命周期也就结束了,然后就被销毁;
类型紧接着一个括号,括号里可以有参数,也可以无参数;
int main() {
//匿名对象
//生命周期只有一行,一般作用不大,但有时比较有用:
int();//这个匿名整型变量是0
int(10);
A();
A(10);
cout << int() << endl;
cout << int(10) << endl;
A().Print();
A(10).Print();
return 0;
}
优化减少类成员函数调用步骤
int main(){
//原本:只为了调用类内函数而创建类A对象a
A a(20);
a.Print();
//优化
A().Print();
A(10).Print();
return 0;
}
编译器的优化 – 需要记住
编译器对涉及类的构造和拷贝构造连续的步骤可能会进行优化,省去中间某个步骤,提高效率; 这就要求我们在与类对象创建时尽可能以少量步骤来完成同样的功能 这些优化当然是在不影响正确性的前提 下进行的
class A {
public:
A(int a = 1)
:_a(a){
cout << "构造函数: A(int a)" << endl;
}
A(const A& a) {
cout << "拷贝构造: A(const A& a)" << endl;
_a = a._a;
}
private:
int _a;
};
int main() {
//对 单参数或多参数构造函数的隐式类型转换 可能的优化
A a1 = 10;//构造+拷贝构造 --> 构造
}
void Func1(A a) {
;
}
int main() {
//对 传参时 可能的优化
A a2(10);
Func1(a2);//先构造在拷贝构造
Func1(A(10));//先构造再拷贝构造 --> 直接构造
}
A Func2() {
A a(10);
return a;
}
int main() {
//对 函数返回时 可能的优化
Func2();//构造+拷贝构造
A ret = Func2();//构造+拷贝构造+拷贝构造 --> 构造+拷贝构造
}
//传值的极致优化
A Func3() {
return A(10);//构造+拷贝构造
}
int main() {
//传值返回极致优化
A ret = Func3();//构造+拷贝构造+拷贝构造 --> 构造
}
编译器优化也不是无脑优化的,编译器的优化是以正确性为基底的,也就是说编译器不做可能会导致之后程序错误的优化;
A Func2() {
A a(10);
return a;
}
int main() {
//下方编译器无法优化
A _ret;
_ret = Func2();//构造+构造+拷贝构造
}
本节主要介绍了类和对象相关概念进行的补充,比如初始化列表,友元,内部类,匿名对象等;
类和对象相关的概念比较多且关联性很强,综合应用下我们很容易头疼和晕头转向。类和对象确实不太好学,但是这是C++的重点章节,也是基础,我们必须要K.O
了它!
加油!