初始化列表
初始化列表是构造函数的一种形式
1.发明缘由
类的成员变量的声明和定义是不同时的
只有在实例化对象时,成员变量才会开始定义
这样也并不是定义,1和2是类似函数的缺省值,并不是赋值。如果初始化时没有赋值,缺省值才会使用。
我们知道const变量和引用的定义必须和声明同时完成,所以如果成员变量有这几个的话,那么类对象将无法完成实例化。所以C++祖师爷开发了初始化列表
2.语法
每个成员变量最多定义一次
int _a1=1和int _a2=2是缺省,如果初始化列表没有初始化,则使用缺省值,所以_a1=1,_a2=2。
再比如
如上,_a1=1,_a2=1。然后执行大括号里的内容,_a1++,_a2–。所以最后_a1=2,_a2=0。
3.补充
上述有两个必须要在初始化列表定义的变量
1.const变量
2.引用
其实还有第三个。
结合构造函数的知识点,默认构造函数,对于内置类型不做处理,对于自定义类型,会调用其构造函数。
B类的构造函数是有参构造函数,B类在A类中,A类会去调用B类的构造函数,但因为是有参构造,所以必须传参,但因为A类的初始化列表没有给B的构造函数传参,所以报错。
所以,对于没有默认构造的自定义类型,也必须在初始化列表定义。
注意:成员变量的定义顺序是同声明一样
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();
}
初始化列表是先定义_a2,然后再定义_a1。所以cout的结果是,_a2是随机值,_a1是1。
explicit
发明缘由
class A
{
public:
A(int aa)
:_a1(aa)
{
cout << "构造函数" << endl;
}
A(const A&aa)
:_a1(aa._a1)
{
cout << "拷贝构造" << endl;
}
private:
int _a1;
int _a2;
};
int main()
{
A aa1(1);//构造函数
A aa2 = 1;//隐式类型转换
int i = 1;
double d = i;//隐式类型转换
return 0;
}
aa1(1)和aa2=1其实是不一样的
aa2=1其实和int赋值给double那样,会创建临时变量,然后再通过拷贝构造函数,进行赋值
但C++11会进行优化,原本是拷贝+构造,优化后,就只通过构造就进行了赋值
我们可以拒绝这样的隐式转换,就是通过explicit关键字
class A {
public:
// 隐式转换构造函数
A(int x) : _x(x) {}
int getX() const { return _x; }
private:
int _x;
};
int main() {
A a = 10; // 编译器会隐式调用 A(int x) 构造函数
cout << a.getX() << endl; // 输出 10
// 使用 explicit 关键字后
class B {
public:
explicit B(int x) : _x(x) {}
int getX() const { return _x; }
private:
int _x;
};
// B b = 20; // 错误,不能隐式调用 explicit 构造函数
B b(20); // 必须显式调用构造函数来创建对象
cout << b.getX() << endl; // 输出 20
return 0;
}
3.编译器版本影响
以上都是单参数的构造函数,C++98支持单参数的隐式类型转换
但是不支持多参数
但是C++11支持,不过形式不同
是将传参用花括号包含
static成员变量&成员函数
1.引子
如果让我们创建一个可以知道实例化了几个对象的类,我们怎么写?
第一种方法,我们可以在全局域创建一个全局变量。
第二种方法,我们可以使用static成员变量和成员函数
class A
{
public:
A(int aa)
:_a1(aa)
{
count++;
cout << "构造函数" << endl;
}
A(const A&aa)
:_a1(aa._a1)
{
count++;
cout << "拷贝构造" << endl;
}
//静态成员函数,访问静态成员变量
static int GetCount()
{
return count;
}
private:
int _a1;
int _a2;
//静态成员变量既属于类,又属于每个对象
//如果设为公有,那么既可以通过对象访问,也可以通过类访问
static int count;//声明
};
int A::count=0;//定义
首先,
一 . static成员变量,以下称为静态成员变量。
他的定义和声明较为特殊,且声明不能有缺省值。定义需要在全局区定义
二. 静态成员变量既属于类,又属于每个对象。即如果为公有,则既可以通过对象访问,又可以通过类访问
比如
int main()
{
//如果count为公有
A aa1(1);
cout<<aa1.count<<endl;
cout<<A.count<<endl;
}
如果静态成员变量是私有的,那么则需要静态成员函数对其操作。
静态成员函数没有this指针
//静态成员函数,访问静态成员变量
//没有this指针
static int GetCount()
{
return count;
}
int main()
{
//支持直接通过类访问
//如果没有设置为静态,则必须通过对象来调用
cout << A::GetCount() << endl;
}
2.小总结
1.静态成员为所有类对象所共享,不单独属于某个具体的对象,存放在静态区
2.静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
3.类静态成员即可用 类名 : : 静态成员或者 对象 . 静态成员来访问
4.静态成员函数没有隐藏的this指针,不能访问任何非静态成员
5.静态成员也是类的成员,受public,protected,private访问限定符的限制
友元
友元可分为友元函数和友元类
1.友元函数
在编写 运算符重载实践 — 日期类 的流插入和流提取时,我们就使用过友元函数,效果是,可以让全局函数访问类中的私有成员变量。关键字是friend,此处不再阐述语法
说明:
1.友元函数可以访问类的私有和保护成员,但不是类的成员函数
2.友元函数不能用const修饰
3.友元函数可以在类定义的任何地方声明,不受类访问限定符限制(public等)
4.一个函数可以是多个类的友元函数
5.友元函数的调用与普通函数的调用原理相同
2.友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
class A
{
//B是A的友元类,B可以访问A的成员,但A不能访问B的成员
friend B;
public:
A()
:_a1(1)
,_a2(1)
{}
private:
int _a1;
int _a2;
};
class B
{
public:
B()
:_b1(1)
,_b2(1)
{}
private:
int _b1;
int _b2;
};
注意:
1.友元关系是单向的,不具有交换性,B是A的友元,但A不一定是B的友元
2.友元关系不能传递,B是A的友元,C是B的友元,A和C不一定有关系
3.友元关系不能继承,详细将会在继承继续学习
3.内部类
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的独享参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
1.内部类可以定义在外部类的public等,但是会受其约束
2.注意内部类可以直接访问外部类的static成员,不需要外部类的对象/类名
3.sizeof(外部类)=外部类的大小,和内部类没有关系,在空间上,两者是互相独立的
class A
{
private:
static int k;
int h = 1;
// 内部类 -- 跟A是独立,只是受A的类域限制
// B天生就是A的友元
public:
class B
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
private:
int b = 2;
};
};
int A::k = 1;
int main()
{
A aa;
cout << sizeof(aa) << endl;//4个字节,只代表A的大小,和B无关
A::B bb;
return 0;
}
本章用于记笔记,如果有不对或者不足的地方,欢迎大佬们指正,补充。感谢大家的阅读,如果感觉博主写的还可以,麻烦点个赞支持一下,阿里嘎多。