目录
初始化列表
介绍
构造函数的函数体内并不是对象成员变量初始化的地方,而是对象成员变量进行赋值的地方,初始化列表才是对象成员变量初始化的地方。
验证的方法很简单,在类里面添加必须进行初始化才能使用的成员变量看编译器报不报错,如:const对象、引用对象。
初始列表位于构造函数的函数体外,使用方式:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式
class Date
{
public:
Date(int year = 0, int month = 0, int day = 0)
: _year(year) //初始化列表
, _month(month)
, _day(day)
{
; //构造函数的函数体
}
private:
int _year;
int _month;
int _day;
};
初始化列表是构造函数的一部分,二者是相辅相成的 ,初始化列表一般进行初始化操作,构造函数体一般用来执行一些逻辑判断、条件检查、内存分配等操作。
特性
- 每个成员变量在初始化列表中最多只能出现一次(即初始化只能初始化一次)
- 不管你是否使用初始化列表,所有的非静态成员变量都会走一边初始列表,没有指定初始内容(指定值或缺省值)就是随机值
- 以下成员变量只能在初始化列表经行初始化
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时)
- 对于自定义类型成员,可以在初始化列表中显示调用它的构造函数,如果你不显示调用编译器会自动去调用默认构造函数
class A
{
A(int a)
:_a(a),
_b(2023,8,17) //显示调用自定义类型的构造函数
{
;
}
A()
:_a(0)
//对于_b编译器会去调用默认构造函数
{
;
}
int _a;
Date _b;
};
- 成员变量在类中声明顺序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
class Date
{
public:
Date(int year = 0,int month = 0, int day = 0)
:_day(day), //初始化的顺序是: _year,_month,_day
_month(month), //初始化的顺序是类中声明顺序,与这里的顺序无关
_year(year)
{
;
}
private:
int _year;
int _month;
int _day;
};
隐式类型转换
如何理解下面这段代码?
class Date
{
public:
Date(int year = 0,int month = 0, int day = 0)
:_day(day),
_month(month),
_year(year)
{
;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1 = 10;
const Date& d2 = 20;
return 0;
}
隐式类型转换,在初始化和赋值时如果类型不匹配会发生隐式类型转换。
前一个表达式:先是用10构造一个临时对象,在用这个临时对象来拷贝构造d1,后一个表达式:先用20构造一个临时对象,在用d2引用这个临时对象,临时对象具有常性所以d2的类型才是const Date&。
现在的编译器对于这种在同一行的连续的构造或拷贝构造会进行优化,如上述代码中,会直接用10去构造d1。
注:如果没有合适的构造函数,就会造成构造失败
如果不想发生隐式类型转换可以用explicit关键字来修饰构造函数,禁止隐式类型转换。
static成员
介绍
被static修饰的成员称为静态成员,静态成员分为静态成员变量和静态成员函数
static int b; //静态成员变量
static void print() //静态成员函数
{
;
}
特性
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员变量定义在全局,需要用域限定符进行声明
int Date::b = 10;
- 类对象在实例化时会给缺省值,静态成员不属于类对象,也就不能给缺省值
- 静态成员变量是因为在全局定义的所以才可以使用域限定符来访问
- 静态成员函数是因为没有this指针所以才可以使用域限定符来访问
- 静态成员函数没有默认的this指针,不能访问任何非静态的类成员函数
- static不可以修饰默认成员函数
友元
在类中有三种访问限制,访问的限制在于除本类域外的其他域是否可以访问我的成员。私有和保护只能在本类域中被访问这样的好处是增加了代码的封装性,但是这也带来了诸多不便。所以C++中提供了一种可以突破封装(访问限定)的方法友元。
对于友元的理解是:我虽然不属于你的一部分不可以访问你的成员,但是我可以成为你的 “朋友” ,这样一来我作为你的 “朋友” 就有资格去访问你的成员。
友元分为:友元函数和友元类。
友元函数
我们可以将函数声明成一个类的友元(朋友),即友元函数,这样就可以在该函数的内部直接访问类的私有和保护成员。
class Date
{
public:
friend Date add(Date& x, Date& y); //友元函数声明
private:
int _year;
int _month;
int _day;
};
Date add(Date& x, Date& y) //突破访问限定符
{
Date d;
d._year = x._year + y._year;
d._month = x._month + y._year;
d._day = x._day + y._day;
return d;
}
特性
- 友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于这个类,但需要在这个类的内部声明,声明时需要加friend关键字
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
友元类
一个类可以时另外一个类的友元(盆友),这个类中的任意一个成员函数都可以访问另一个类中的非公有成员
class A;
class Date
{
public:
friend A; //友元类声明
Date(int year = 0,int month = 0, int day = 0)
:_day(day),
_month(month),
_year(year)
{
printf("构造\n");
}
Date(Date& d)
{
printf("拷贝构造\n");
}
private:
int _year;
int _month;
int _day;
};
class A
{
public:
Date add(Date& x, Date& y) //可以访问另一类非公有成员
{
Date d;
d._year = x._year + y._year;
d._month = x._month + y._year;
d._day = x._day + y._day;
return d;
}
};
- 友元关系是单向的,不具有交换性,如上述代码中A类的成员函数可以访问Date类非公有成员,但Date类的成员函数不能访问A类非公有成员
- 友元关系不能传递,如果C是B的友元, B是A的友元,则不能说明C时A的友元。
- 友元关系不能继承
内部类
介绍
如果一个类定义在另一个类的内部的话就被称为内部类。内部类与外部类的关系是:首先内部类是一个独立的类它并不属于外部类,其次由于内部类定义在外部类中所以必须的通过外部类来间接访问内部类。
class B
{
public:
class A
{
public:
int a;
};
};
int main()
{
B::A d1; //通过外部类间接访问内部类
return 0;
}
特性
- 由于内部类定义在外部类中,所以内部类受到外部类访问限定符的限制
class B
{
private: //私有
class A
{
public:
int a;
};
};
int main()
{
B::A d1; //编译错误,无法访问私有成员
return 0;
}
- 内部类天生就是外部类的友元类
- sizeof(外部类)等于外部类对象大小,和内部类没有任何关系
- 内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
匿名对象
介绍
匿名对象就是在对象实例化时,不为该对象取名字。匿名对象可以在调用一个构造函数后紧跟着在调用一个成员。
class A
{
public:
int _a;
int _b;
};
int main()
{
A(); //匿名对象
int s = A()._a ; //可以接着在调用一次类成员
return 0;
}
特点
- 匿名对象的声明周期只有一行,一行之后自动调用析构函数在释放空间
- 匿名对象与临时对象一样具有常属性
- 匿名对象不可在全局定义
- 如果引用匿名对象会延长匿名对象的生命周期,延长至当前局部域的生命周期