目录
前序
一、初始化列表
这到底是初始化列表还是构造函数?
初始化列表存在的必要性
初始化列表的功能只有这样吗?
私有成员对象的隐形坑
总结
二、类型转换
三.static成员
1.为什么说静态成员变量不属于某个具体对象?
2.为什么说静态成员变量为所有类对象共享,不属于某个具体的对象?
四、友元与内部类
1.友元函数
2.友元类
形象比喻:
3.内部类
前序
大家好,我是小沐。前面的文章里面我们已经共同学习了默认成成员函数中的构造函数,但是构造函数的知识还有一部分等着我们去挖掘,也就是本篇的核心内容——初始化列表,当然还有其它补充内容。
一、初始化列表
以前我们初始化成员变量主要是在函数体内赋值,但是构造函数初始化还有另外一种方式——初始化列表。
理清初始化列表和构造函数的关系:初始化列表和构造函数是一体的。
我们一起来认识
#include <iostream>
using namespace std;
class A
{
public:
A(int n = 2)
:_a(n)
, _b(_a)
{
cout <<"_a:" << _a << endl;
cout <<"_b:" << _b << endl;
}
private:
int _a;
int _b;
};
int main()
{
A d;
return 0;
}
看到这,很多小白可能就疑惑了,这和构造函数有什么区别呢?
答案是没有区别,以前我们没有显式写初始化列表时,编译器是自动帮我们生成了默认列表,就和自动生成默认成员函数一样。
这到底是初始化列表还是构造函数?
这当然是构造函数,初始化列表是构造函数的一部分。 :号后面,{}前面的这一部分就是初始化列表。以前我们在函数体内初始化叫做函数体内赋值。
注:如果形参定义了却没有定义缺省值,就不是构造函数,而是一个纯正的初始化列表
:_a(n)
, _b(_a)
这一部分叫做初始化列表
A(int n = 2)
:_a(n)
, _b(_a)
{
cout <<"_a:" << _a << endl;
cout <<"_b:" << _b << endl;
}
而整体叫做构造函数。
这时好学的你肯定想问:在函数体内赋值就可以完成的工作,增加一个初始化列表不就是纯多余吗?
初始化列表存在的必要性
以前我们都学过const修饰变量,那让我们来看这个例子
const a;
const& b;
a = 1;
b = 2;
这种写法是不是有着很大的问题,我们都知道const修饰常量,被const修饰的变量和const引用的变量必须在定义的时候就初始化。可如果是成员变量呢?
class A
{
public:
A(int n = 2)
:_a(n)
, _b(_a)
{
cout <<"_a:" << _a << endl;
cout <<"_b:" << _b << endl;
}
private:
int _a;
int _b;
const int& _c;
};
这样的你又怎么给他赋值呢?有的同学可能说,我直接在定义的时候给它一个初始值就行了嘛,像这样
class A
{
public:
A(int n = 2)
:_a(n)
, _b(_a)
{
cout <<"_a:" << _a << endl;
cout <<"_b:" << _b << endl;
}
private:
int _a;
int _b;
const int& _c = 1;
};
那么请问,如果我这一次使用程序想让_c是另一个经常使用的重要常数,怎么办?你这种写法就已经在底层给它写死,不就达不到自己的预期了吗?这时候初始化列表的作用就体现出来了。
class A
{
public:
A(int a = 2,int b = 2, int c)
:_a(a)
, _b(b)
,_c(c)
{
cout <<"_a:" << _a << endl;
cout <<"_b:" << _b << endl;
}
private:
int _a;
int _b;
const int& _c = 1;
};
这样就可以根据需要来定义常量值了,const 变量和const引用的定义初始化问题就解决了
初始化列表的功能只有这样吗?
当然不止于此,初始化列表还可以使用于表达式和没有默认构造的类类型变量,如
class Time
{
public:
Time(int hour)
:_time(hour)
{
cout << "_time:" << _time << endl;
}
private:
int _time;
};
class Date
{
public:
Date(int a = 2,int b = 2, int c = 2)
:_a(a)
, _b(b)
,_c(c)
,_T(1) //可以是类类型变量
,_ptr((int*)malloc(sizeof(int)*4)) //括号里面可以是表达式
{
if (_ptr == nullptr)
{
perror("_ptr::malloc");
return;
}
cout <<"_a:" << _a << endl;
cout << "_b:" << _b << endl;
cout <<"_c:" << _c << endl;
}
~Date()
{
free(_ptr);
_ptr = nullptr;
}
private:
int _a;
int _b;
const int& _c;
Time _T;
int* _ptr;
};
int main()
{
Date d;
return 0;
}
很明显,这样就大大提高初始化列表的使用灵活性。当然这里还有一种操作,我们先来看问题。
私有成员对象的隐形坑
下面程序的运行结果结果是什么?
A. 输出 1 1
B. 输出 2 2
C. 编译报错
D. 输出1 随机值
E. 输出 1 2
F. 输出 2 1
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();
return 0;
}
我们来看编译结果

在回答为什么之前,你先问自己,下面这段代码它是初始化吗?
private:
int _a2 = 2;
int _a1 = 2;
在私有域里面这种叫做声明,没有初始化,等号后面的值表示缺省值,在编译这里时系统并没有给它开空间。
回到前面的问题,为什么输出值不是1 1
在初始化列表中,初始化的顺序不是按照列表里面的代码顺序来逐个初始化的,而是按照在private中声明的先后顺序来进行初始化的。原因是物理内存中是按声明顺序进行存储的。
所以,在进入初始化列表后,编译器会先对_a2进行初始化,而_a2是用_a1的值初始化的,这个时候的_a1是随机值,导致赋值给_a2后也是随机值。
这里如果理解模糊的同学也可能会问
按照这个规则,_a1不是有默认值吗?应该会用默认值赋值给_a2才对啊,怎么会是随机值呢?
因为_a1在初始化列表里面是用了n进行初始化的,而当选择了用n进行初始化后,_a1的缺省值也就作废了,编译器已经不认它了,只认n,导致没有初始化到_a1时它就是一个随机值。
总结

二、类型转换
C++支持内置类型隐式转换为类类型对象,但需要有相关内置类型为参数的构造函数。
class A
{
public:
// 构造函数explicit就不再⽀持隐式类型转换
// explicit A(int a1)
A(int a1)
:_a1(a1)
{
}
//explicit A(int a1, int a2)
A(int a1, int a2)
:_a1(a1)
, _a2(a2)
{
}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
int Get() const
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
B(const A& a)
:_b(a.Get())
{
}
private:
int _b = 0;
};
int main()
{
// 1构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa3
// 编译器遇到连续构造+拷⻉构造->优化为直接构造
A aa1 = 1;
aa1.Print();
const A& aa2 = 1;
// C++11之后才⽀持多参数转化
A aa3 = { 2,2 };
// aa3隐式类型转换为b对象
// 原理跟上⾯类似
B b = aa3;
const B& rb = aa3;
return 0;
}
可以看到,C++是可以让类类型像C语言中不同类型之间隐式转换一样,不同于C语言的是,C++发生隐式类型转换会创建临时变量,创建临时变量就会调用拷贝构造,再将临时变量返回赋值,不过现在的编译器已经把这个过程进行优化,会直接构造。
三.static成员
用static修饰的成员变量就叫做静态成员变量,修饰成员函数就叫做静态成员函数。
static成员特点:
1.静态成员变量为所有类对象共享,不属于某个具体的对象,不存在对象中。
2.静态成员函数没有this指针。
3.静态成员函数可以访问其它的静态成员,但由于没有this指针导致不能访问非静态的成员。
4.非静态成员函数可以访问任意静态成员和静态成员函数。
5.静态成员也是类的成员,受public、protect和private访问限定符的限制。
6.静态成员变量不能在声明位置给缺省值,因为缺省值是构造函数初始化列表的,静态成员函数不属于某个对象,不走构造函数初始化列表。
1.为什么说静态成员变量不属于某个具体对象?
思考一下,类A的大小是多少
class A
{
public:
void Print()
{
cout << sizeof(A) << endl;
}
private:
static int _a;
static int _b;
};
int main()
{
A aa;
aa.Print();
return 0;
}
公布答案

这里的1个字节是占位符的大小,表示类A存在,所以说static成员变量是不存在某一个具体对象里面的。
2.为什么说静态成员变量为所有类对象共享,不属于某个具体的对象?
class A
{
public:
A()
{
_A++;
}
~A()
{
_A–;
}
void Print()
{
cout << sizeof(A) << endl;
}
int GetStaticA()
{
return _A;
}
private:
static int _A;
};
int A::_A = 1;
int main()
{
A a1;
A a2;
cout << a1.GetStaticA() << endl;
cout << a2.GetStaticA() << endl;
return 0;
}
我们把_A放在构造函数里面,如果_A属于某个具体的对象,那么a1和a2输出的结果应该都是2,如果是共享的,那么a1初始化时_A加1,a2初始化时又加1,输出结果应该都是3

所以说静态的成员函数是同类类型的对象共享的,并不属于某一个具体的对象。又因为它是属于A类,现定义另一个类B,B是不能访问_A,说明静态成员函数是受访问限定符限制的。其它的特点在刚刚的代码里面也已经展现出来了的。
四、友元与内部类
我们都知道,两个类之间是不能直接访问彼此的私有成员变量的,友元就给我们提供了一种突破访问限定符封装的方式。
友元:1.友元函数
2.友元类
1.友元函数
class B;
class A
{
public:
friend void func(const A& aa, const B& bb);
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
friend void func(const A& aa, const B& bb);
private:
int _b1 = 3;
int _b2 = 4;
};
void func(const A& aa, const B& bb)
{
cout << aa._a1 << endl;
cout << bb._b1 << endl;
}
int main()
{
A aa;
B bb;
func(aa, bb);
return 0;
}
在之前,我们类外面是不能直接访问私有的成员变量的,但是现在我把func放到A和B里面,用friend进行修饰,就相当于在A和B的朋友名册里面加上了一条:func是我的好朋友;那好朋友拿自己的成员变量看一下,只是允许的。
2.友元类
友元类的原理和友元函数一样,把B方到A里面用friend修饰,A就会认为B是A的好朋友,允许B访问A的私有成员变量。
class B;
class A
{
public:
friend class B;
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
void func(const A& aa, const B& bb)
{
cout << aa._a1 << endl;
cout << bb._b1 << endl;
}
private:
int _b1 = 3;
int _b2 = 4;
};
int main()
{
A aa;
B bb;
bb.func(aa, bb);
return 0;
}
这样B就能直接访问A的私有成员变量了,但是这里A能不能访问A的成员变量呢?

显然A是不能直接访问B的私有成员变量的。
形象比喻:
定义类A和类B,并且在A中用friend修饰B。A男同学暗恋B女同学,默默把她放在心中,但是B同学不知道,当B同学想找A同学借一些私人性质比较强的东西时,A大概率会借;而反过来,A同学找B同学借的话,B会比较排斥。
3.内部类
如果一个类定义在另一个类的内部,这个内部的类就叫做内部类。虽然内部类是定义类外部类的里面,但是它却和成员函数类似,也是一个独立的类,在计算外部类的大小时并不包含在里面。
class A
{
public:
class B
{
public:
private:
int _b1 = 1;
};
void Print()
{
cout << sizeof(A) << endl;
}
private:
};
int main()
{
A aa;
aa.Print();
return 0;
}

可以看到,输出结果仅有占位符的大小,说名内部类确实是独立与外部类的,外部内定义对象中是不包含内部类的。
但是这里又有个特别有意思的事情,外部类会把内部类当友元类,而内部类却没有把外部类当作友元,和上一节的友元类如出一辙,我们就不再解释。
内部类本质也是一种封装,A类跟B类紧密关联,A类实现出来主要就是给B类使用的。


