再谈构造函数
函数体内初始化
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
return 0;
}
上述代码,在创建对象d时,会调用构造函数对d进行初始化。
构造函数调用之后,对象中已经有了一个初始值,但是不能将其称作为类对象成员的初始化,构造函数体中的语句只能将其称作为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
注:只有构造函数有初始化列表
class Date
{
public:
Date(int year=1,int n)
/初始化列表可以认为就是对象成员变量定义的地方
/在定义的时候初始化(只能初始化一次)
:_n(n)
,_ref(value)
{
/在定义时不初始化,在定义后赋值
_year=year;
}
private:
int _year;
/只能在定义初始化
const int _n;
int& _ref;
}
注:
每个成员变量在初始化列表中只能出现一次(初始化只能出一次)
类中包含以下成员,必须放在初始化列表位置进行初始化(因为这些类型变量必须在定义时初始化):
引用成员变量
const
成员变量自定义类型成员(该类没有默认构造函数)
自定义类型成员(该类没有默认构造函数) 在一般情况下,我们写类时,都需要写默认构造函数,是因为它可能做其他类的成员变量,在创建含有类成员的类对象时,会对这个类初始化,就是对这个类的每一个成员变量初始化,对这个成员类初始化,要调用这个类成员的默认构造函数。 如果是对成员类初始化就要调用默认构造函数 对类(非成员类)初始化就正常 如果是自定义类型成员(该类没有默认构造函数),就需要在定义时初始化,写在初始化列表 其实就是在构造函数, 使用初始化列表是(在定义时初始化) 在函数体中(先定义,后赋值) class A { public: A() { x = 1; } private: int x; }; class Date { public: Date(int year=0, int n=0, int a=0) :_n(n)// 使用初始化列表是(在定义时初始化) { _year = year;// 在函数体中(先定义,后赋值) } void Print() { cout << _year << _n << _a << endl; } private: int _year = 1; int _n = 2; int _a = 3; A aa; }; int main() { Date D(4,7); D.Print(); return 0; }
程序运行:
尽量使用初始化列表初始化
如果不写构造函数,成员变量也会在自动生成的默认构造函数的初始化列表走一遍,类成员会调用默认构造函数,内置类型会使用缺省值初始化(如果没给缺省值,就是随机值)
如果不写构造函数,会自动生成默认构造函数,在自动生成的默认构造函数的初始化列表会调用类成员的默认构造函数初始化成员类;内置类型也会在初始化列表给缺省值。
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
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(); } A. 输出1 1 B.程序崩溃 C.编译不通过 D.输出1 随机值
解答:
选择 D 解析: 创建A类型对象aa,调用构造函数,先执行_a2(_a1),再执行_a1(a)
explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。
class Date { public: Date(int year) { _year=year; } private: int _year; } int main() { Date d1(2022);//构造 Date d2=2022;//隐式类型转换(会产生中间变量) /隐式类型转换的过程是,2022先构造成一个中间变量,再将中间变量拷贝构造给d2 /优化后(部分编译器会对这种隐式类型转换) /直接构造d2 return 0; }
将上述代码中的构造函数前加上explicit,将会禁止隐式类型转换
explicit关键字的作用是禁止发生类型转换
补充:
优化:部分编译器会对连续的拷贝构造(在一句代码中)优化成一次拷贝构造或构造+拷贝构造(在一句代码中)优化成一次构造
习题:
以下代码在<不考虑优化>和<考虑优化>的情况下分别共调用多少次拷贝构造函数? Widget f(Widget u) { Widget v(u); Widget w=v; return w; } main() { Widget x; Widget y=f(f(x)); }
答: <不考虑优化>:9次 <考虑优化> :7次
static成员
概念
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化
静态成员变量
class A
{
private:
//属于整个类,不在初始化列表定义
static int _count1;//不可以给缺省值,因为缺省值是在初始化列表使用的,但静态变量不在初始化列表定义
}
//在类外面定义
int A::_count1=0;
共有静态变量
class A
{
private:
static int _count;/
}
//在类外面定义
int A::_count=0;
int main()
{
A aa;
cout<<a.count<<endl;//访问共有静态变量
cout<<A::count<<endl;//访问共有静态变量
return 0;
}
静态函数
- 静态成员函数没有this指针
- 不能访问非静态成员,没有this指针
友元
友元分为:友元函数和友元类
友元函数
- 谁想访问我,谁就要成为我的友元函数
- 友元函数并不属于这个类
- 没有传递关系
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性
友元关系不能传递
如果B是A的友元,C是B的友元,则不能说明C时A的友元。
内部类
概念
如果一个类定义在另一个类的内部,这个内部类就叫做内部类。
注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。 注意:
内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
内部类可以定义在外部类的public、protected、private都是可以的。
内部类天生是外部类的友元,内部类可以直接访问外部类中的私有。
sizeof
(外部类)=外部类,和内部类没有任何关系。class A { private: static int k; int h; public: class B//B天生是A的友元(B可以访问A,但A不能访问B) { public: void foo(const A& a) { cout<<k<<endl; cout<<a.h<<endl; } privite: int _b; }; }; int main() { A a; int n = sizeof(a); cout<<n<<endl; //打印4 return 0; }