目录
1.初始化列表
1.1初始化列表引入
由上面的基础知识知道,我们创建一个对象后编译器会调用默认的构造函数对这个对象的成员变量进行初始化,但他的本质是赋值操作而不是初始化操作,因为初始化只能进行一次而构造函数调用会进行好多次。
1.2初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
class Date
{
public:
//构造函数: -->初始化列表初始化
Date(int year = 2022, int month = 10, int day = 6)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
1.3注意
1、初始化列表可以认为就是对象成员变量定义的地方
2、每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
3、类中包含以下成员,必须放在初始化列表位置进行初始化:
- ·引用成员变量
- ·const成员变量
- ·自定义类型成员(该类没有默认构造函数)
其他变量既可以在初始化列表也可以在函数体内部初始化。一般建议用初始化列表初始化。
4、尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。5、初始化列表不能初始化静态成员变量(static)
6、成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
就比如这个题,打印的是1,随机值。
谁先声明先初始化谁,所以就先初始化a2,a1刚开始没有被初始化他就是随机值,所以a2就是随机值,后边a1被初始化为1,所以是1,随机值。
2.explicit关键字
关键字explicit可以阻止隐式转换 的发生。
构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。一般涉及到隐式类型转换。如果我们不想让他转换就可以用explicit。
class Date { public: Date(int year) :_year(year) {} private: int _year; }; int main() { Date d1(2022); //构造 Date d2 = 2022; //构造 + 拷贝构造 +优化--》 构造 }
就比如d2,2022不是一个日期类,是一个整形所以就会发生类型转换。所以先构造一个临时变量tmp,将类型转换后的2022放在tmp中,然后调用拷贝构造将天tmp的的值拷贝给d2。 编译器进行一系列优化后就会直接调用构造。如果我们加上explicit就不允许这种情况发生。
直接报错了就。
举一个更直观的例子
explicit
关键字用于限制隐式类型转换。它可以用于类构造函数前面,防止编译器自动执行隐式转换。如果一个类构造函数前面带有explicit
关键字,则不能将该构造函数用于隐式类型转换。这意味着,必须显式地创建一个该类的对象,才能调用该构造函数。class A { public: A(int x) : m_x(x) {} // 普通构造函数 private: int m_x; }; class B { public: explicit B(int x) : m_x(x) {} // 显式构造函数 private: int m_x; }; int main() { A a = 1; // 隐式类型转换 B b = 2; // 错误:不能隐式类型转换 B c(3); // 正确:必须显式创建对象 return 0; }
在上面的示例中,
A
类的构造函数是普通构造函数,可以被用于隐式类型转换。而B
类的构造函数是显式构造函数,不能被用于隐式类型转换。因此,当我们试图用整数值初始化B
类对象时,会得到编译错误。
3.static 成员
3.1static分类
static声明的类成员称为类的静态成员,分为如下两类:
- 用static修饰的成员变量,称之为静态成员变量
- 用static修饰的成员函数,称之为静态成员函数
静态的成员变量一定要在类外进行初始化。
3.2static的特点
(1)类中的成员共享,所有对象共享,不属于某个具体的类,存放在静态区。属于整个类 也属于类中所有对象。
(2)类中只能声明,类外才能定义,定义时不添加static关键字。
(3)静态成员函数可以不使用对象进行访问,只要突破类域就可以调用,可以使用类名::静态成员或者对象.静态成员来访问。
(4)静态成员函数没有this指针,不能访问非静态成员。
(5)静态成员也是类的成员,受public、protected、private 访问限定符的限制。
对(1)性质理解,所有对象公有。
3.3静态成员变量
1.我们定义了两个成员变量,但是对于_a来说每一个对象都会创建一个_a在其成员变量中,而_scount是存放在静态区是他们的公有财产所有对象公有一个静态成员变量。
2.静态成员变量必须在类外面进行定义。要不就会报错。但是必须要公开成员变量。
3.4静态成员函数
- 静态成员函数是没有this指针的,因为它属于类,不属于任何实例对象。
- 静态成员函数可以访问静态成员变量,但是不能访问非静态成员函数,因为非静态成员变量是在对象实例化的时候创建的。
- c++ 不支持静态构造函数。
静态成员函数主要有以下几个用途:
访问静态数据成员:静态成员函数只能访问静态数据成员,因为静态数据成员是与类相关联的,而不是与类的对象相关联的。
在类的对象之间共享数据:静态数据成员是所有类的对象共享的,因此静态成员函数可以在类的对象之间共享数据。
避免创建类的对象:有时候,我们只需要调用类的函数,而不需要创建类的对象。这种情况下,静态成员函数可以派上用场。
实现单例模式:单例模式是一种常用的设计模式,它保证一个类只有一个实例,并提供了一个访问该实例的全局访问点。静态成员函数可以用来实现单例模式。
假设我们有一个名为
Math
的类,其中包含一个静态成员函数Add
,可以将两个整数相加并返回它们的和。我们可以通过创建静态函数来调用Add
函数,而无需创建Math
类的对象,如下所示:#include <iostream> using namespace std; class Math { public: static int Add(int x, int y) { return x + y; } }; int main() { int a = 5, b = 3; int sum = Math::Add(a, b); // 调用静态函数Add cout << "Sum of " << a << " and " << b << " is " << sum << endl; return 0; }
在上面的例子中,我们使用
Math::Add
来调用静态成员函数Add
,而不需要创建Math
类的对象。这种方法使代码更加简洁和可读,同时还允许我们使用类的成员函数,而无需创建类的对象。
class A
{
private:
static int m_value;
public:
static int getValue() { return m_value; } // static member function
};
int A::m_value {1}; // 初始化,即使在定义在private下,仍然可以访问
int main()
{
std::cout << A::getValue() << std::endl; // can be acccessed by class A
}
静态成员函数不受访问限定符的限制,即使在private下面仍然在类外面初始化。
再看一下这个例子
class Sum{
public:
Sum()
{
_i++;
_tmp+=_i;
}
static int GetSum()
{
return _tmp;
}
private:
static int _i;
static int _tmp ;
};
int Sum::_i=0;
int Sum::_tmp=0;
class Solution {
public:
int Sum_Solution(int n) {
Sum a[n];
return Sum::GetSum();
}
};
最后是我们通过类域来调用的。
3.5析构函数调用
设已经有A,B,C,D4个类的定义,程序中A,B,C,D析构函数调用顺序为? ( ) C c; void main() { A*pa=new A(); B b; static D d; delete pa; } A.A B C D B.A B D C C.A C D B D.A C B D
答案是B
这道题主要考察的知识点是 :全局变量,静态局部变量,局部变量空间的堆分配和栈分配
其中全局变量和静态局部变量时从 静态存储区中划分的空间,
二者的区别在于作用域的不同,全局变量作用域大于静态局部变量(只用于声明它的函数中),
而之所以是先释放 D 在释放 C的原因是, 程序中首先调用的是 C的构造函数,然后调用的是 D 的构造函数,析构函数的调用与构造函数的调用顺序刚好相反。
局部变量A 是通过 new 从系统的堆空间中分配的,程序运行结束之后,系统是不会自动回收分配给它的空间的,需要程序员手动调用 delete 来释放。
局部变量 B 对象的空间来自于系统的栈空间,在该方法执行结束就会由系统自动通过调用析构方法将其空间释放。
之所以是 先 A 后 B 是因为,B 是在函数执行到 结尾 "}" 的时候才调用析构函数, 而语句 delete a ; 位于函数结尾 "}" 之前。
4友元和内部类
4.1友元
C++中的友元是一种机制,可以让一个函数或类访问另一个类的私有成员,即使它不是该类的成员函数。友元分为友元函数和友元类两种类型。
- 友元函数
友元函数是在一个类中声明并定义的非成员函数。它可以访问该类的私有成员和受保护成员。要将一个函数声明为类的友元函数,需要在类的声明中使用
friend
关键字。示例代码:
class A { public: friend void func(A& a); // 声明友元函数 private: int x; }; void func(A& a) { // 友元函数的定义 a.x = 10; // 可以访问私有成员 } int main() { A obj; func(obj); // 调用友元函数 return 0; }
- 友元类
友元类是指一个类能够访问另一个类的私有成员和受保护成员。在类的声明中使用
friend
关键字声明一个类为友元类。示例代码:
class A { public: friend class B; // 声明类B是类A的友元类 private: int x; }; class B { public: void func(A& a) { // 类B中的成员函数可以访问类A的私有成员 a.x = 10; } }; int main() { A obj; B b; b.func(obj); // 调用类B中的成员函数,修改类A中的私有成员 return 0; }
需要注意的是,过度使用友元会破坏封装性和安全性,因此应谨慎使用。友元的作用应该是在确保封装性和安全性的前提下,提供更方便的访问方式。
4.11友元函数特点
(1)友元函数可以访问类的私有和保护成员,但不是类的成员函数。
(2)友元函数不能用const修饰
(3)友元函数可以在类中任何地方声明,不受类访问限定符限制
(4)一个函数可以是多个类的友元函数
(5)友元函数的调用与普通函数的调用原理相同
4.12友元类特点
- 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 友元关系是单向的,不具有交换性。(c是b的朋友,b和你是朋友,但是你不一定认识c)
- 友元关系不能继承。
- 友元关系不能传递。
class Date; // 前置声明 class Time { friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量 public: Time(int hour = 0, int minute = 0, int second = 0) : _hour(hour) , _minute(minute) , _second(second) {} private: int _hour; int _minute; int _second; }; class Date { public: Date(int year = 1900, int month = 1, int day = 1) : _year(year) , _month(month) , _day(day) {} void SetTimeOfDate(int hour, int minute, int second) { // 直接访问时间类私有的成员变量 _t._hour = hour; _t._minute = minute; _t._second = second; } private: int _year; int _month; int _day; Time _t; };
我在时间类中声明日期类是他的友元类,那日期类就可以访问时间类的非公有成员。
4.2内部类
内部类就是在类里在写一个类呗,就像套娃一样。
1. 如果一个类定义在另一个类的内部,这个内部的类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。与普通类没有太大的差别。
2. 内部类天生是外部类的友元类,可以访问外部类的所有成员,但是外部类不是内部类的友元,不能访问内部类的成员。
3.内部类可以定义在外部类的public、protected、private都是可以的。
4.注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
内部类可以访问其外部类的私有成员和保护成员,这使得内部类在实现某些功能时非常有用。