摘要:初始化列表,explicit关键字,匿名对象,static成员,友元,内部类,编译器优化
类是对某一类实体(对象)来进行描述的,描述该对象具有哪些属性、哪些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象。
1. 初始化列表
构造函数函数体内是对成员变量进行赋值,而不是初始化!
初始化列表:成员变量定义的地方
为什么规定初始化列表是所有成员变量定义的地方?
对于以下三类特殊的成员变量类型,初始化列表的存在是必要的:
- const 成员变量—— const 变量必须在定义时初始化
- 引用 —— 引用必须初始化
- 自己没有默认构造函数的自定义类型变量
warning:Initialization is not assignment. Initialization happens when a variable is given a value when it is created. Assignement obliterates an object's current value and replaces that value with a new one.
Four different ways to define an int variable named units_sold and initialize it to 0:
- int units_sold = 0;
- int units_sold = {0}; //list initialization (C++11)
- int units_solc{ 0 }; //list initialization (C++11)
- int units_sold(0);
——《C++ Primer》
Use
- 冒号开始,逗号分隔,初始化值/表达式 用括号括起来。示例如下。
- 成员变量走初始化列表的顺序为成员变量声明的顺序.(如下图,先用v2初始化v1,而v2还未初始化所存储的为随机值)
- C++规定初始化列表是每个成员变量定义的地方,每个成员变量都会走初始化列表(所以成员变量的初始化尽量使用初始化列表),定义的时候不给初始值——即在初始化列表没有显式写的成员变量——未被初始化。
- 在初始化列表没写的成员变量,对于内置类型将不做处理——初始化值为随机值(但因编译器不同而异),对于自定义类型默认调用它自己的构造函数
sum.能用初始化列表就用初始化列表,但同时有些场景需要初始化列表和构造函数体混用。(根据具体的需求而异)
补充:C++11 支持在成员变量声明的时候给缺省值,这个缺省值是给初始化列表用的,如果初始化列表没有显式给初始值,就用缺省值作为初始化值。
2. explicit 关键字
- C++98 支持单参数隐式类型转化
- C++11 进一步支持多参数隐式类型转化
在构造函数前加 explicit 使得不能进行隐式类型转换:(示例如下)
class B
{
public:
explicit B(const int b1, const int b2)
{
_b1 = b1;
_b2 = b2;
}
explicit B(const B& b)
{
_b1 = b._b1;
_b2 = b._b2;
}
private:
int _b1;
int _b2;
};
3. 匿名对象
- 匿名对象:生命周期仅在改匿名对象定义的这一行。e.g.A(7);
- 有名对象:生命周期在该作用域。e.g.A aa(7);
用途:
- 传参(如果单/多参数隐式类型转化被禁)
- 定义对象就是为了调用成员函数 → 就可以用匿名对象去调用
- ……(匿名对象的用途是十分广泛的)
const引用会延长匿名对象的生命周期
const引用其实使得匿名对象变成了有名对象:引用给了匿名对象别名,这个“别名”的生命周期即为被引用的匿名对象的生命周期。e.g.const A& ref = A();
4. static 成员
如果我们有如下两个需求:
- 统计累计创建了多少个对象
- 统计正在使用的对象还有多少个
统计累计创建的对象:首先,我们可以选择创建全局变量来统计。同时,因为创建对象会自动调用构造函数,所以我们在构造函数体内对这个用于统计的变量进行++操作,这样每次创建一个对象,就会调用并执行构造函数,每次执行都会累计对这个全局变量++。
统计正在使用的对象:对象销毁会自动调用析构函数,创建的对象 - 已经析构的对象 = 正在使用的对象。
int e = 0;//统计累计创建的对象
int now_e = 0;//统计仍在使用的对象
class A
{
public:
A(const int a = 0)
{
++e;
++now_e;
_a = a;
}
~A()
{
--now_e;
}
private:
int _a;
};
问题:不够封装,全局变量可能会被随意修改,将造成需求之外的影响。 → “封装” → 将变量放入类中 → 私有变量无法访问 → static 成员
static 成员变量
static 成员变量的性质:
- 属于所有对象——即整个类,而不是某一个对象。
- 不能给缺省值。
- 不走初始化列表,初始化列表是某个对象的初始化,而 static 成员变量不属于某个对象。
- 位于静态区。
static 成员变量的声明与定义:类内声明,类外定义(静态成员变量一定要在类外进行初始化)
class A
{
private:
static int m;//static成员变量声明
};//这样写A相当于空类
int A::m = 0;//static成员变量定义
访问 static 成员变量的两种情况下的方式:
- 如果 static 成员变量是 public 的:①创建对象访问;②通过指针访问(这里的指针只是为了突破类域,而不会解引用,之前在类和对象上的this指针关于空指针的内容有详细解释);③直接访问。
class A { public: static int m;//static成员变量声明 };//这样写A相当于空类 int A::m = 0;//static成员变量定义 int main() { A aa; aa.m;//①创建对象访问; A* p = nullptr; p->m;//②通过指针访问 A::m = 1;//③直接访问 return 0; }
- 如果 static 成员变量是 非public 的:提供函数接口访问,这样可以使得 static 成员变量是只读不可修改的(引用返回可修改,这个根据需求自己控制),不同于public的情况。
class A { public: int GetM() { return m; } private: static int m;//static成员变量声明 }; int A::m = 0;//static成员变量定义 int main() { A aa; cout << aa.GetM() << endl; return 0; }
static 成员函数
static 成员函数的性质:
- 没有 this 指针,不能访问非静态成员变量
- 访问方式同 static 成员变量——突破类域即可访问
5. 友元
-
友元函数
特性:
- 可以访问类的 private 和protected 的成员,但友元函数不是类的成员函数。
- 不能用 const 修饰(因为不是成员函数更没有 this 指针,const 修饰的是 this 指针)。
- 可在类定义的任何地方声明,不受访问限定符限制。
- 一个函数可以是多个类的友元函数。
- 友元函数的调用同普通函数
class Date
{
friend ostream& operator<<(ostream& out, Date d);//友元函数声明
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, Date d)//函数定义
{
out << d._year << "/" << d._month << "/" << d._day << endl;
return out;
}
-
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。(如下代码,B类在A类友元声明之后,B类中的所有成员函数相当于在A类中都进行了友元声明——可以访问A类的非公有成员。注意:A类不可以访问B类的非公有成员。)
友元类的性质:
①单向性(如下,B可以访问A,但A不可以访问B);
②友元关系不传递(B是A的友元,A是C的友元,不能得出B是C的友元);
③不继承。
class A
{
friend class B;
private:
int _a;
};
class B
{
void Print(A _aa)
{
cout<<_aa._a;
}
//A aa;//注意:这个只是声明,不开空间
};
6. 内部类
内部类:在类中再定义一个类。内部类仅受外部类的类域和访问限定符限制,此外内部类与外部类是两个独立的类。
使用场景:内部类可以应用于当你想定义一个类而仅在某个类中使用时。
注意:如上图,C不可以访问D的非公有成员, D在C类域之内又封装了一层,并且根据友元类的单向性也是如此。
7. 拷贝对象时编译器的一些优化
此部分内容仅作了解,在之前旧版本的博客有较为详细的讲述,详细内容可参见本文:C++ -4- 类和对象(下)
END