目录
1.再谈构造函数之初始化列表
1.1初始化列表的引入
我们知道,在创建类对象时,编译器会自动调用构造函数,从而对该对象实现初始化操作
类和对象(上)介绍了构造函数的特性,
但当时我们讲的是在构造函数的函数体内实现初始化操作的
事实上,那应该叫作函数体内赋值操作,因为初始化只能进行一次
函数体内却还可以多次赋值
1.2初始化列表的格式
成员变量的初始化操作,应该使用初始化列表
初始化列表就是成员变量定义的地方,它在构造函数主体之前执行
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括 号中的初始值或表达式。
1.3初始化列表的注意事项
(1)
A aa(1, 10);
这可以看作是在整体初始化一个对象,但对象的成员变量的初始化操作
的的确确是在初始化列表进行的,
初始化列表包含在构造函数中,如果不显示写,编译器也会隐式生成一个
这时,对于内置类型会去看它是否有缺省值,从而在初始化列表进行使用
当然,当我们显示传递之后,肯定是选择用我们传递的值
对于自定义类型会去调用自定义类型的构造函数,以完成自定义类型的初始化
(1)引用成员变量
const成员变量
自定义类型成员(且该类没有默认构造函数时)
class Point { public: int x, y; Point(int x_val, int y_val) : x(x_val), y(y_val) {} // 自定义构造函数 }; class Rectangle { private: Point topLeft; // 自定义成员 int width, height; public: // 使用初始化列表初始化topLeft成员 Rectangle(int x, int y, int w, int h) : topLeft(x, y) , width(w) , height(h) {} // 初始化列表 void print() const { std::cout << "TopLeft: (" << topLeft.x << ", " << topLeft.y << "), " << "Width: " << width << ", Height: " << height << std::endl; } };
必须在初始化列表进行初始化
它们的共同特点是,在被创建时就需要进行初始化操作
(1)在进入构造函数主体时, 引用已经需要被绑定到一个对象。初始化列表是构造函数的一部分,它在构造函数主体执行之前运行,因此是初始化引用的唯一机会(1)编译器要求所有
const
成员变量在进入构造函数主体之前(即在初始化列表中)完成初始化(1)没有默认构造函数,编译器无法自动初始化该成员。因此,必须在初始化列表中显式调用构造函数,以确保成员对象被正确构造
(1)函数体内只是赋值操作,不是初始化操作!!
类型 必须初始化列表的原因 const
成员构造后不可修改,必须一次性初始化 无默认构造的自定义类型 编译器无法隐式调用无参构造 引用成员 必须在声明时绑定,后续不可修改指向
(1)成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
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并未初始化,所以a2是随机值,然后初始化a1为1
2.explicit关键字
对于接收单个参数的构造函数,还具有类型转换的作用
接收单个参数的构造函数具体表现:
1. 构造函数只有一个参数
2. 构造函数有多个参数,除第一个参数没有默认值外,其余参数都有默认值
3. 全缺省构造函数
我们使用赋值符号来创建Date类对象d1
因为类型不同,编译器会进行隐式类型转化操作
先将int类型转换为一个Date类的临时对象,再用这个临时对象拷贝构造对象d1
但现在的编译器基本都进行了优化操作,直接用int类型构造d1对象
但当我们用explicit修饰构造函数时,我们就阻止了隐式类型转化操作的发生
3.static成员
3.1static成员的介绍
声明为static的类成员称为类的静态成员
用static修饰的成员变量,称之为静态成员变量,静态成员变量一定要在类外进行初始化
用static修饰的 成员函数,称之为静态成员函数
3.2static成员的特性
1.静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
在类外进行定义时,要声明类域
class Date { private: static int _year; }; int Date::_year = 2025;
3.静态成员变量受private限制时,就不像全局变量一样可以任意被访问
但当类静态成员属于公有时即可用 类名::静态成员 或者 对象.静态成员 来访问
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
意思就是 静态成员函数没有this指针,就不能访问到this指向的对象的成员变量,只能访问类中的静态成员变量,因为静态成员变量属于类,为所有对象所共享
5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
1.静态成员函数可以调用非静态成员函数吗?
不能,因为静态成员函数没有this指针,无法传递给非静态成员函数中的this指针相应值
2. 非静态成员函数可以调用类的静态成员函数吗?
可以,静态成员函数不需要this指针,在类中可以被任意调用
静态成员函数不属于“通过对象调用”的范畴,而是通过类调用的
4.友元
友元突破了封装,需谨慎使用
4.1友元的介绍
在C++中,友元(friend)是一种机制,
它允许一个类或函数访问另一个类的私有(private)和保护(protected)成员。
友元不是类的成员函数,但它可以像类的成员函数一样访问类的私有和保护成员。
友元关系打破了类的封装性,因此在使用时需要谨慎
----文心一言
友元分为 友元类 和 友元函数
4.2友元函数
4.2.1友元函数的引入
以 << 重载为例
我们想要实现 cout << d1,即让cout打印一个自定义类型,此时就需要进行运算符重载
但是,<< 重载不能重载为成员函数,因为成员函数中this指针充当了第一个参数
即,this指针指向的对象是默认的左操作数,那么我们最终的实现只能是 d1 << out
这不符合常规使用,我们选择将 << 重载为全局函数,全局函数就会遇到下面的困境:
私有成员不可访问
运用友元函数,便会解决这个问题
class Date
{
friend ostream& operator<<(ostream& out, const Date& d);
private:
int _year = 2025;
int _month = 3;
int _day = 18;
};
ostream& operator<<(ostream& out, const Date& d)
{
cout << d._year << '-' << d._month << '-' << d._day;
}
int main()
{
Date d1;
cout << d1 << endl;
return 0;
}
友元函数是指被类的
friend
关键字修饰的非成员函数理解:
(1)函数在类外定义,想成为友元函数,就必须在类中进行声明,并用friend修饰
4.2.2友元函数的特性
1.友元函数可访问类的私有和保护成员,但不是类的成员函数
友元函数可以视为朋友,成员函数可以视为家人,邀请朋友做客时,
家中的零食都可以给他吃,但他终究不是家人
2.友元函数不能用const修饰
友元函数不存在this指针,修饰无意义
3.友元函数可以在类定义的任何地方声明,不受类访问限定符限制
友元函数不是类的成员函数,访问限定符对它不起作用,
声明只是为了"告诉类,它是朋友"
4.一个函数可以是多个类的友元函数
就像一个人可以是多个人的朋友一样
5.友元函数的调用与普通函数的调用原理相同
4.3友元类
4.3.1友元类的概念
友元类(Friend Class)是C++中的一个概念,它允许一个类(友元类)的所有成员函数访问另一个类(被访问类)的私有成员和保护成员
4.3.2友元类的特性
1.友元关系是单向的,不具有交换性。
class Time { friend class Date; private: int _hour; int _minute; int _second; };
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time 类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行
2.友元关系不能传递 如果B是A的友元,C是B的友元,则不能说明C时A的友元。
就像c是b的朋友,b是a的朋友
3.友元关系不能继承,在继承位置再给大家详细介绍
5.内部类
5.1内部类的概念
定义在一个类(外部类)中的类(内部类),就叫作内部类
内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员
外部类对内部类没有任何优越的访问权限
class Date
{
public:
class Time
{
private:
int _hour;
int _minute;
int _second;
};
private:
int _year = 2025;
int _month = 3;
int _day = 18;
};
Time类就是一个内部类
创建一个内部类对象时,需要指明类域
Date::Time t;
5.2内部类的特性
(1)内部类就是外部类的天生友元类,但是外部类不是内部类的友元类
记忆方法:谁累谁服,累->内,服->friend
(1)内部类定义在外部类的public、protected、private都是可以的
(1)内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
我是的你的朋友,在你家中直接使用就好
(1)sizeof(外部类)=外部类,和内部类没有任何关系
一个被创建的外部类对象并不会包含一个内部类对象,
所以一个包含内部类的外部类所占空间大小就等于一个外部类所占空间大小
6.临时对象
6.1临时对象的简介
创建一个对象,该对象没有名字,形如
6.3临时对象的创建
1.手动创建
类名(参数);
是否传参根据实际需求
2.自动创建
当我们函数传值返回时,也会生成一个临时对象,例如:
Date Date::operator-(int day) const { Date temp = *this; temp -= day; return temp; }
返回temp时,编译器就会调用拷贝构造函数,生成一个临时对象
6.3临时对象的特性
(1)临时对象的生命周期只在当前行
即,159行的临时对象的生命周期只在159行
(1)临时对象调用成员函数
因为临时对象的生命周期只在当前行,所以随创建随调用
(1)临时对象具有常性,取别名时,需要用const修饰
当临时对象被绑定到一个引用上去后,生命周期与引用一致
const Date& ra = Date();
这是特性,记住即可
7.
连续的构造,会被编译器优化