目录
一、构造函数相关知识点补充
1.构造函数体赋值
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
虽然调用构造函数后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
2.初始化列表
初始化列表是C++中用于初始化成员变量的一个特性,在类的构造函数中,可以使用初始化列表来直接初始化成员变量,而不是在构造函数体内进行赋值。
形式:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
注意点:
①每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
②类中包含引用成员变量、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(0)
{}
private:
A _aobj; //没有默认构造函数
int& _ref; //引用
const int _n; //const
};
这段代码中,类B的成员变量_aobj是类A实例化出的一个对象,而A中是没有默认构造函数的,而_ref是引用,_n是const成员变量,所以它们初始化必须在初始化列表中,否则编译器会报错。
为什么这三种必须要在初始化列表初始化呢?
我们知道,引用必须在定义时初始化,并且一旦被初始化后就不能再被重新赋值,而构造函数体中执行的就是赋值,所以必须在初始化列表中初始化。而const修饰的变量在初始化后是不能被修改的,它也必须在定义时初始化,若执行构造函数体中的赋值语句便是修改了。对于没有默认构造函数的自定义类型,我们不能依赖编译器为我们自动创建对象,必须显式地在初始化列表中为它们提供一个合适的构造函数参数来初始化。
关于第③点:
初始化列表不管写不写,每个成员都会走一遍,自定义类型的成员会调用它的默认构造函数,如果没有默认构造函数就会报错,内置类型有缺省值就用缺省值,缺省值是给初始化列表用的,没有的话,要看编译器,有的编译器会处理,有的不会。
关于第④点:
先看一段代码:
在上述代码中,倘若是按照初始化列表中的次序初始化,那最后输出结果应当是1 1,而实际上不是,这就是因为是按照成员变量声明顺序来初始化的。
3.explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换作用。
#include <iostream>
using namespace std;
class A
{
public:
A(int x)
:_a(x)
{}
void Print()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A a = 1;
a.Print();
return 0;
}
最后这段代码的输出结果是1,说明语句A a = 1;是可行的,a跟1类型不一样为什么可以这样写呢?实际上是发生了隐式转换,编译器会先用1构造一个临时对象,然后再调用拷贝构造函数讲临时对象拷贝给a,所以最后是没有问题的。另外,编译器遇到连续构造+拷贝构造会优化为直接构造。
对于只有一个参数的构造函数,我们可以直接用A a = 1;这种形式来写,让它发生隐式转换,对于多个参数的其实也可以,但是就需要用{},例如A a = {1,2,3};。
而explicit关键字主要作用是防止构造函数被用于隐式类型转换,当我们在构造函数前加上explicit关键字后,就不能隐式转换了。
二、static成员
声明为static的类成员称为类的静态成员,用static修饰的成员变量称为静态成员变量,用static修饰的成员函数称为静态成员函数。静态成员变量一定要在类外进行初始化。
特性:
①静态成员为所有类所共享,不属于某个具体的对象,存放在静态区
②静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
③类静态成员可用类名::静态成员或者对象.静态成员来访问
④静态成员函数没有隐藏的this指针,不能访问任何非静态成员
⑤静态成员也是类的成员,受public、protected、private访问限定符的限制
⑥静态成员函数只能调用静态成员函数,不能调用非静态成员函数,而非静态成员函数可以调用静态成员函数也可以调用普通成员函数
假设有一个类A,有一个静态成员变量_a,在类外定义时这样写:int A::_a = 0;
注意:静态成员变量是不能给缺省值的,缺省值是给初始化列表用的,而静态成员变量在静态区,不存在对象中,不走初始化列表。
从这段代码运行结果可以看到,静态成员变量的大小并不包含在类的大小中, 所以说静态成员变量不存在对象中也进一步可以验证了。
还有一个注意点,一般类都写在头文件中,但是静态成员变量不能直接在头文件中直接初始化,因为当有多个源文件时,如果都包含该头文件,就会导致重定义。
三、友元
1.友元函数
友元函数是通过关键字friend来修饰的,友元函数必须在类的定义中说明,其函数体可以在类内定义,也可以在类外定义。一个函数可以被多个类声明为朋友,这样就可以引用多个类中的私有数据。
注意:
①友元函数可以访问类的私有和保护成员,但是不是类的成员函数
②友元函数不能用const修饰
③友元函数可以在类定义的任何地方声明,不受访问限定符限制
④一个函数可以是多个类的友元函数
⑤友元函数的调用与普通函数的调用原理相同
2.友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
注意:
①友元关系是单向的,不具有交换性
②友元关系不能传递,例如A是B的友元,B是C的友元,是不能说明A是C的友元的
③友元关系不能继承
示例:
#include <iostream>
using namespace std;
class B
{
friend class A;
public:
B(int x = 1)
:_b(x)
{}
private:
int _b;
};
class A
{
public:
A(int x = 0)
:_a(x)
{}
void Test()
{
b._b = _a;
}
private:
int _a;
B b;
};
内部类:
概念:如果一个类定义在另一个类的内部,这个类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员,外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员,但是外部类不是内部类的友元。
特性:
①内部类定义在外部类的public、private、protected都可以
②内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
③sizeof(外部类) = 外部类,和内部类没有任何关系
看一段代码来验证:
#include <iostream>
using namespace std;
class A
{
private:
static int k;
int h;
public:
class B // B天生就是A的友元
{
public:
void test(const A& a)
{
cout << k << endl;
cout << a.h << endl;
}
};
};
int A::k = 1;
int main()
{
cout << sizeof(A) << endl;
A::B b;
b.test(A());
return 0;
}
运行结果:
可以看到,sizeof(A)= 4,也就是只算了成员变量h的大小,要访问A类中的静态成员变量k也是可以直接访问。