前言
前面已经对类和对象有一定的了解,接下来再次深入的了解一下。
一、深入理解构造函数
构造函数体赋值:
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化, 构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体 内可以多次赋值
class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; };
初始化列表
初始化列表是在C++中构造函数中用于初始化类成员变量的一种机制。
- 它以冒号(
:
)开始,后跟以逗号分隔的成员变量列表,每个成员变量后面可以跟一个初始化表达式。
class Date { Date(int year, int month, int day) :_year(year) , _month(month) , _day(day) {} private: int _year; int _month; int _day; };
初始化列表使用的关键点:
- 初始化顺序:成员变量在初始化列表中的初始化顺序遵循它们在类定义中声明的顺序,而不是初始化列表中出现的顺序。
- 严格执行声明顺序即初始化顺顺序,下面的代码在语法上先进行capacity的初始化。
下面的程序会崩溃:
- 先进行_a的开辟空间,capacity没有进行初始化
- 导致capacity 是随机值。赖皮空间随机值。
class Stack { public: Stack(int capacity)//错误 :_capacity(capacity) , _a((int*)malloc(sizeof(int)* _capacity)) ,_size(0) {} private: int* _a; int _capacity; int _size; };
特定成员的初始化:对于常量成员、引用成员以及没有默认构造函数的自定义类型成员,必须在初始化列表中进行初始化,因为这些成员不能在构造函数体内被赋值。
- 引用和const修饰都是在初始化定义的。
class A { public: A(int a) :_a(a) {} private: int _a; }; class B { public: B(int a, int ref) :_aobj(a) ,_ref(ref) ,_n(10) {} private: A _aobj; // 没有默认构造函数 int& _ref; // 引用 const int _n; // const };
- 性能优势:使用初始化列表可以避免成员变量先默认构造后再赋值的额外步骤,特别是对于资源管理类(如持有指针或动态分配内存的类),这可以提高效率
- 成员变量的初始化只能进行一次,无论是在初始化列表中还是在构造函数体内,都不能重复初始化同一个成员变量。
class Date { Date(int year, int month, int day) :_year(year) , _month(month) , _day(day) // ,_day(day) //在加入这句就是错误的,初始化只能进行一次。 {} private: int _year; int _month; int _day; };
- 成员变量的缺省值:如果在类成员声明时指定了缺省值,这些值将在进入构造函数体之前被用于初始化列表
- 如果在定义的时候进行了传递参数,即优先使用传递的参数。
class Date { Date(int year, int month, int day) :_year(year) , _month(month) , _day(day) {} private: int _year = 1; int _month = 1; int _day = 1; };
- 初始化列表的灵活性:除了初始化成员变量,初始化列表还可以用于动态内存分配、类型转换赋值等操作
二、explicit 关键字
explicit
关键字用于修饰类的构造函数,以防止隐式类型转换。当一个构造函数被声明为explicit
时,它不能参与隐式转换,只能通过直接初始化或拷贝初始化的方式显式调用。
class Date { public: // 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用 // explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译 explicit Date(int year) :_year(year) {} Date& operator=(const Date& d) { if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this; } private: int _year; int _month; int _day; }; void Test() { Date d1(2024); d2 = 2024; }
三、static成员
- 声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量
- 用static修饰的 成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化
static成员的特性
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
在下面的例子中:
- 没有this指针 func1 访问不了 func2
class Date { public: static void func1() { } void func2() { } private: int _year; int _month; int _day; static int count; };
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
四、友元
友元概念
- 友元(friend)是一种特殊的关系,它允许一个函数或类访问另一个类的私有成员或保护成员。
- 友元关系不是类的成员关系,而是一种独立的访问权限关系。
- 通过友元,可以在不破坏封装性的前提下,实现类之间的数据共享和功能协作
修改print:
正常访问"重载操作<<",是访问不到 Date的private成员变量的,friend很好的帮助我们突破了这个界限。
ostream& operator<<(ostream& out,const Date& d) { out << d._year << "年" << d._month << "月" << d._day << "日" << endl; return out; } class Date { friend ostream& operator<<(ostream& out, const Date& d); public: Date(int year = 1, int month = 1, int day = 1) : _year(year) , _month(month) , _day(day) {} private: int _year; int _month; int _day; };
五、内部类
内部类的特点
- 独立性:内部类是一个独立的类,不隶属于外部类。
- 访问权限:内部类可以定义在外部类的任何访问级别中,其访问外部类成员的权限取决于其自身的定义位置。
- 访问外部类成员:内部类可以直接访问外部类的
static
和enum
成员,但不能直接访问外部类的非静态成员。 - 大小关系:内部类的大小与外部类无关,
sizeof(外部类)
不会包括内部类的大小。
- 创建方式:内部类的对象可以通过外部类的作用域限定符创建,例如
外部类名::内部类名
。 - 生命周期:内部类的对象可以在栈上或堆上创建,其生命周期取决于创建方式。
{ private: static int k; int h; public: class B // B天生就是A的友元 { public: void foo(const A& a) { cout << k << endl;//OK cout << a.h << endl;//OK } }; }; int A::k = 1; int main() { A::B b; b.foo(A()); return 0; }
编程题目:友元的巧妙应用
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
数据范围: 0<�≤2000<n≤200
进阶: 空间复杂度 O*(1) ,时间复杂度 O*(n)
class Solution { public: class B { public: B() { ret +=i; ++i; } }; int Sum_Solution(int n) { B a[n]; return ret; } private: static int ret ; static int i; }; int Solution:: ret = 0; int Solution:: i = 1;