const成员
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this 指针,表明在该成员函数中不能对类的任何成员进行修改。
其实很简单,就是在类内函数()后加上const就可以把原来类型为Date*的this指针变成const Date*类型.
const修饰的变量是不能传给没有被const修饰的引用和指针的,因为我们不能const修饰的变量被修改.
被const修饰的变量传给没有被const修饰的指针和引用时被称为权限放大是不被允许的.
而没被const修饰的变量,传给被const修饰的指针和引用时被称为权限的缩小是被允许的.
如我们在const修饰的this指针函数中无法调用没有被const修饰的.
如
原因也很简单,我们的函数在传参的时候会默认把this指针也传给调用的函数,我们上述的例子就是把Print函数的this指针传给了GetMonthDay函数,但是Print的this指针是const而GetMonthDay函数的不是const,这种传参属于是权限的放大.
但是非const就可以传给const这是权限的缩小.
初始化列表
我们使用构造函数函数,其实并不是对其进行初始化,而是对其进行赋值
有点像我们的构造函数和下面类似
int a;
a = 10;
但是我们有的时候必须是要对变量进行初始化,而不是赋值.
所以我们就需要到初始化列表了.
经过初始化列表进行初始化的就是直接类似:int a = 10;
初始化列表的格式如下:
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
class Date { Date(int year = 2002, int month = 8, int day = 26) :_year(year) , _month(month) , _day(day) { ; } private: int _year; int _month; int _day; };
如下
注意:
每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
类中包含以下成员,必须放在初始化列表位置进行初始化
引用成员变量
const成员变量
自定义类型成员(该类没有默认构造函数)
尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使 用初始化列表初始化。
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
例子如下:
class test { public: test() :_a1(1) , _a2(_a1) { //cout << "test()" << endl; } void Print() { cout << "_a1:" << _a1 << endl; cout << "_a2:" << _a2 << endl; } private: int _a2; int _a1; }; int main() { test a; a.Print(); return 0; }
运行结果如下:
而如果我们先初始化_a2在通过_a2 传给_a1就是正常情况,如下:
explicit关键字
介绍explicit之前让我们先看一串代码吧.
class test { public: test(int a = 1, int b = 1) { _a = a; _b = b; } void Print() { cout << "_a>:" << _a << endl << "_b>:" << _b << endl; } private: int _a; int _b; }; int main() { test a; a = { 10,20 };//特殊的赋值,对代码的可读性不好 a.Print(); return 0; }
浅提一下我们这种特殊赋值的实现是通过先建立一个临时的类再通过拷贝构造传给我们的对象a.我们新的编译器会对这个过程进行优化具体请看[小拓展—编译器优化](# 小拓展—编译器优化)
我们的explicit关键字就可以避免这个问题.
在构造函数前面加上explicit就将那种赋值给避免了.
小拓展
我们的这个赋值方式的成功是我们的编译器在赋值之前创建了一个临时变量,然后通过临时变量与类型a通过拷贝构造将10,20的值给到a中.
static成员
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的 成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化.
面试题:实现一个类,计算中程序中创建出了多少个类对象。
#include<iostream> using namespace std; class A { public: A() { ++_scount; } A(const A& t) { ++_scount; } static int GetACount()//静态修饰的成员函数没有this指针 { return _scount; } private: static int _scount; }; int A::_scount = 0;//对类内进行初始化,不能在类内初始化 void TestA() { cout << A::GetACount() << endl;//静态成员函数可以这样调用. A a1, a2; A a3(a1); cout << a1.GetACount() << endl; }
上述代码中是因为static修饰的成员变量是整个类共享的不会因为换了个对象而改变自身值.
特性
静态成员为所有类对象所共享,不属于某个具体的实例
静态成员变量必须在类外定义,定义时不添加static关键字
类静态成员即可用类名::静态成员或者对象.静态成员来访问
静态成员函数没有隐藏的this指针,不能访问任何非静态成员
静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值
而非静态的成员函数可以调用静态的成员函数.
C++11成员初始化更新
C++11支持非静态成员变量在声明时进行初始化赋值,但是要注意这里不是初始化,这里是给声明的成员变 量缺省值。
他的作用类似于给初始化列表一个缺省值.
友元
友元可以分为1. 友元类 2.友元函数
使用友元我们可以突破封装,但是也会增加耦合性,所以友元要尽量少用
友元函数
我们在重载操作符的时候有两个操作符<< >>无法重载因为我们的重载调用的时候会把左边的当做this指针,而我们需要把cout放在左边所以我们就需要用的我们的友元函数了.
注: cout只是我们的一个调制好的ostream类的一个全局对象.内置类型的打印已经写好了,所以我们只需要把类里需要打印的通过内置类型打印好即可.
还是以日期类为例子吧:
我们实现一个日期类的<< 来看看吧.
class Date { public: Date(int year = 2002, int month = 8, int day = 26) { _year = year; _month = month; _day = day; } ostream& operator<<(ostream& out) { out << _year << "_" << _month << "_" << _day << endl; return out; } private: int _year; int _month; int _day; };
这种实现我们要使用的话十分别扭.
int main() { Date a1; a1 << cout;//日期类的<<的使用 return 0; }
我们就可以用到友元来实现
实现如下:
class Date { public: friend ostream& operator<<(ostream& out, Date& d); Date(int year = 2002, int month = 8, int day = 26) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; }; ostream& operator<<(ostream& out, Date& d)//返回这样的类型是因为<<的结合性是从左到右 { out << d._year << "_" << d._month << "_" << d._day << endl; return out; } int main() { Date a1; cout << a1;//正确的使用 return 0; }
所以友元的使用也是很容易看出来的.
友元函数的使用就是在类里将函数声明前加上friend 即可.
当我们的函数是类的友元的时候我们就可以访问类的私有空间.
所以友元在函数方面的作用其实就是让一个普通函数具有访问一个类私用空间的权利
注意
友元函数可访问类的私有和保护成员,但不是类的成员函数
友元函数不能用const修饰
友元函数可以在类定义的任何地方声明,不受类访问限定符限制
一个函数可以是多个类的友元函数
友元函数的调用与普通函数的调用和原理相同
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
举例如下:
class Date { public: friend class test; Date(int year = 2002, int month = 8, int day = 26) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; }; class test { public: void Print() { cout << a._year << endl; } private: Date a; };
上面的例子我们的test就是Date的友元类,我们就可以通过在test里创建Date的对象来访问Date类里的私有成员.
注:
友元关系不能传递.
如A是B的友元 B是C的友元 但是A不是C的友元
友元不具有交换性.
比如我们test是Date的友元类所以我们的test可以访问Date的私有成员但是我们的Date不能访问test的私有成员.
内部类
概念: 一个类定义在另一个类的内部就叫内部类.
注意: 内部类是外部类的友元类但是外部类对内部类没有任何优先和权力,对于外部类来说内部类就跟普通的类一样,外部类没有内部类的任何特权.
并且内部类可以不通过对象等做到直接访问外部类的成员(包括:枚举成员(就是C的枚举),static)
特性:
内部类可以定义在外部类的public、protected、private都是可以的。
注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
sizeof(外部类)=外部类,和内部类没有任何关系。
class A { public: class B { void Print(const A& a) { cout << a.h << endl; cout << _a << endl; //上述代码均可以通过 } }; private: int h; static int _a; }; int A::_a = 10;
我们想要访问到类B的话就需要通过A::B() 的方式进行访问了.
小拓展(关于类名+()这个匿名对象)
我们还是弄个日期类吧
class Date { public: Date(int year = 2002, int month = 8, int day = 26) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "_" << _month << "_" << _day << endl; } private: int _year; int _month; int _day; };
ok,现在我们讲一下匿名对象.
创建方式其实很简单类名+()即可如我们的日期类就可以写成Date()这就是个匿名对象.
匿名对象特性
生命周期只有一行,运行完就没了.(除非用常引用来接收才可以)
我们一般用它来调用类里的函数之类的.
int main() { Date().Print(); return 0; }
具体功能咱也不知道.等后续遇见了再补.
小拓展—编译器优化
我们的新的胆大的编译器会把一下需要构造再拷贝构造的或者双次拷贝构造的操作简化成一次拷贝构造或一次构造.(不同编译器不同)
举例来看
注: 例子都是通过以下代码进行的测验
#include<iostream> using namespace std; class test { public: test(int a1 = -1, int a2 = -1) { cout << "test()" << endl; _a1 = a1; _a2 = a2; } test(const test& a) { cout << "test(const test& a)" << endl; } void Print() { cout << "_a1: " << _a1 << endl; cout << "_a2: " << _a2 << endl; } private: int _a1; int _a2; }; test fun(test a) { return a; }
先构造再拷贝构造
int main() { test a1 = 1;//特殊赋值 return 0; }
这个特殊赋值我们在讲述[explicit关键字](# explicit关键字)的时候浅提了一下
具体过程我用下面的图片来解释:
我们的编译器优化后就会省略很多步骤,如下图:
而我们在VS2022也是符合的