面向对象和过程的初步介绍
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
类
类其实类似于我们C语言中的结构体,不过有对类进行了较多的修改使其更加优秀.
在C语言中我们只能向里面放变量,但是在类里我们可以向里面放函数
如下就是一个类
class date
{
public:
void Init(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _day;
int _month;
int _year;
};
C++习惯使用class来创建类,不过你是用struct来创建类也是可以的=.=,最好使用class
而且C++中我们可以不用typedef来省事了直接可以用类名来创建变量.
如我们就可以直接使用
int main()
{
date a;
a.Print();
return 0;
}
类的定义
class className
{
};
类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。
一般向类中放函数可以有两种情况
- 声明定义都放在类中
- 值将声明放在类中,把定义放在.cpp的文件里
其中声明定义都放在类中的就如我们下面举的例子一样.
class date
{
public:
void Init(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _day;
int _month;
int _year;
};
这样放入类中的函数,编译器默认其为内联函数.不过内联函数是建议具体要看编译器.
第二种情况
类似下面代码
我们一般推荐第二种方式.
(本文为了方便讲解先使用第一种方法)
我们类里的函数那怕跟其他的函数重名重类型也是可以使用的,我们通过linux来看看
所以我们并不用担心重名情况.
类的访问限定符与封装
访问限定符
如果我们在C语言中使用结构体来写栈,如果有人不通过我们的函数接口来改变变量也是可以做到的,但是这样写会有很多隐患.
如下图
我们好好的写好的代码就这么被人随便改了,我们后续再写代码的时候直接就一堆错误,非常痛苦.
于是我们的C++语言就加入了public private protected 这三个来保护权力
- public修饰的成员在类外可以直接被访问
- protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- class的默认访问权限为private,struct为public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
class date
{
public:
void Init(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _day;
int _month;
int _year;
};
我们的public下到private之间的内容是都是类之外可以访问的.
而我们private到类定义结束的区间都是我们类之外不能访问的.
类的实例化(对象)
我们想要访问类里的任何元素必须通过对象才可以访问
和我们的C语言结构体一样我们的类也只是一个蓝图而已,他并没有在建造类的时候占据空间,只有在形成变量的时候才会占据空间.
也就是说我们创建的类其实就类似于一个类型,一个自己定义的类型.
我们把用类创建的变量叫做对象.
如何计算类的大小
我们的类中既有函数又有变量那么实例化之后的类的大小如何计算呢?
其实和C语言的结构体相同都是用对齐来计算大小的,我们的类虽然内部可以定义和声明函数但是函数并不占据类实例化后的大小.
而我们在类中的函数其实是存储在公共内存中的,无论对象是否相同
就像我们不在类里的函数一样也都是开辟一次之后就在那一片内存中使用.
所以在类中的函数也是和普通函数一样放在一块公共区域中,然后我们将传入的参数压进去进行操作.
也就不放在类中计算大小了.
如果我们不放变量那么类的大小应该是多大呢?
答案是:1—这个1的意思并不是保存了什么,而是为了占位表示对象存在.
this指针
- this指针的类型:类类型* const
- 只能在“成员函数”的内部使用
- this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
- this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
这个指针被我们的编译器隐藏起来了.
在上面我们定义函数的时候你可以看见
class date
{
public:
void Init(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _day;
int _month;
int _year;
};
我在定义Print函数的时候直接写了_year ,_month ,_day 而我们打印的时候也雀氏会打印这些变量保存的内容,其实原因也很简单我们的C++通过隐藏this指针来得到的元素内容.
其实Print这个函数里面的内容应该是这样的.
void date::Print(date* const this)
{
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
因为是在类里所以private的元素也是可以访问的.
this是C++自己创立自己传参自己修饰,我们只需要写_year 这种变量名即可.
this指针也跟普通形参一样存储在栈中
this指针其实是可以为空的.只要不在函数内解引用this指针就不会报错.
如下:
但是如果函数内对this指针进行了解引用就不可以了.
类<2>
类的6个默认生成函数
类如果是个空类我们的编译器,也会生成6个默认的函数并在符合条件的情况下自己调用.这些函数包括
构造函数,析构函数,拷贝构造,赋值重载,两个取地址重载
这6个函数都是我们可以进行改造的并且在使用的时候编译器会自己调用,非常舒服.
构造函数
比如我们现在创建了一个日期型类,我们想对其进行初始化,但是如果我们只是写了个初始化的函数我们还需要每次使用都调用,非常不方便.
而我们的构造函数作为一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次。
值得注意的是,虽然名字叫构造函数但是他跟对象实例化构造没啥关系,构造函数就只管给成员附上值.
特性
-
函数名与类名相同。 -
无返回值。 (且不是void而是没有返回的类型) -
对象实例化时编译器自动调用对应的构造函数。 -
构造函数可以重载。
因为可以重载所以可以创建多个来方便我们使用
- 在类被其他类引用是构建函数是会被调用.
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;
};
int main()
{
date a;
a.Print();
return 0;
}
上面代码实现的结果如下图:
我们这样创建的构造函数是可以传参的
传参形势如下:
上面的构造函数我们使用了全缺省,但是如果我们不使用全缺省会发生什么呢?
我们将构造函数改成下面形势
改成上面形势后就会报下面的错误说我们没有默认构造函数使用.
那么什么可以成为默认构造函数呢?
[第五点介绍](# 特性)第五点的意思直接看图吧=.=
首先建立一个测试类并搭建好他的构建函数
在另一个类中使用测试类
来看看我们测试类的构造函数有没有被调用
调用了.
默认构造函数
直接告诉大家: 只有全缺省,无参,编译器自动生成的可以做默认构造函数,一个类没有默认构造函数并且没有传参的话是实例化出对象的.
但是如果没有默认构造函数,只要传参得当也是可以实例化出对象的.
比如下面的半缺省.
不过我们在搭建默认构造函数的时候还是使用全缺省较好.
而且全缺省的函数和无参不能同时出现,不然我们在使用的时候编译器无法识别.
注意: 我们类成员变量在取名的时候最后前面加上_ (不同公司规定不同,反正最好不要直接使用对应名称如year ,不然可能会出现以下情况)
因为我们的编译器的this指针是编译器自己调用的,并不是十分智能,所以我们最好还是在前面加上_ 或者在其他地方加上标识.
也可以用this指针来弄,不过给人感觉怪怪的=.=
析构函数
概念
析构函数也不是将类内成员都销毁那是编译器干的事情,析构函数是在对象的生命结束要被销毁的时候自动调用的函数,比如我们的栈类要向堆区要空间,我们就可以在此处进行归还
特征
- 析构函数名是在类名前加上字符 ~.
- 无参数无返回值.
- 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数.
- 对象生命周期结束时,C++编译系统系统自动调用析构函数.
- 在当类一中有其他类的时候,类一的对象在被销毁前会调用其他类的析构函数.
来个例子看看吧
class Stack
{
public:
Stack()
{
_data = (int*)malloc(sizeof(int) * 4);
_top = 0;
_capacity = 4;
}
~Stack()
{
free(_data);
_top = 0;
_capacity = 0;
}
private:
int* _data;
int _top;
int _capacity;
};
拷贝构造函数
概念
用于将一个相同类型的对象内容拷贝到另一个对象中,只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
如果是已存在的拷贝到已存在的是
举例如下
class date
{
public:
date(int year = 2002, int month = 8, int day = 26)
{
_year = year;
_month = month;
_day = day;
}
date(const date& c)
{
_year = c._year;
_month = c._month;
_day = c._day;
}
void Print()
{
cout << _year << "_" << _month << "_" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
这样我们对另一个对象进行初始化时就可以直接传来对象了.
在初始化时使用= 赋值也可以调用到拷贝构造函数
其实我们不写拷贝构造函数我们的编译器也会给我们生成一个,自己生成的拷贝构造函数在浅拷贝的时候完全够用了,当我们需要深拷贝的时候就可以自己写.
深拷贝
既然我们不写拷贝构造函数编译器会自动生成一个用于浅拷贝的,为啥还要有呢?
主要是因为我们要写深拷贝.
深拷贝就是我们在实现的时候注意一下,不能简单通过赋值操作来拷贝的需要深拷贝一下.
比如指针等.
我们比如我们使用Stack类的时候需要从堆区拿空间,就需要指针来保存变量,如果我们使用浅拷贝就会造成free两次的警告,而且在使用的时候也十分诡异.
所以我们要专门写个拷贝构造函数来达到深拷贝的目的.
比如我们的Stack类的.
Stack(const Stack& s)
{
_data = (int*)malloc(sizeof(int) * (s._capacity));
memcpy(s._data, _data, sizeof(int) * s._capacity);
_capacity = s._capacity;
_top = s._top;
}
我们的这些默认生成的函数都可以自己实现,只要自己记住他们的格式就好.
其中取地址重载等函数都可以实现但是,没必要编译器的实现以及足够我们使用了.
操作符重载
我们用类的时候总要使用-,+,=,*,/,++,--,==,!= 等操作符,还是以日期类为例.
当然日期类就一般不会使用*,/ 了=.=
早操作符重载的格式如下
Date& operator=(Date& d1 ,const Date& d2);
而具体的我们要看实现.
比如我们可以选择在类里实现或是在类外实现.
先来一个日期类吧
class date
{
public:
void Init(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _day;
int _month;
int _year;
};
我们先用类外的方式实现一个== 看看吧
bool operator==(const Date& d1, const Date& d2)
{
if (d1._year == d2._year && d1._month == d2._month && d1._day == d2._day)
{
return true;
}
else
{
return false;
}
}
这个其实并不能使用因为我们_year 是私有形势存储的,要想使用就必须将_day,_month,_year 公有化或者使用内部共有函数来得到他们的值,因为我们只是演示,所以我暂时先把他公有化出来.
我们实现好了这个== 运算符后
上图框起的两种方式都可以使用到我们的== 重载功能,但是我们偏向使用下面的a==b .
在我们使用a == b 的时候其实编译器会帮我们换成第一种的形式—operator==(a,b).
不过我们还是把这些运算符放在类里较好,及保证了类的封装性,又保证了我们元素不被外界访问.
我们将操作符重载放在类里需要对格式进行稍微改变.
bool operator==(const Date& d)
{
if (_year == d._year && _month == d._month && _day == d._day)
{
return true;
}
else
{
return false;
}
}
在类内定义的格式其实和外面定义的几乎没啥区别,除了少了一个变量,在使用_year 的时候我们不用把它变成共有化的了.
其实少的那个变量使用了this指针来代替.而_year 其实也是通过this指针得到的.
但是我们的this是两个变量的那个呢?
来看看我们如何使用类里定义的重构函数就知道了.
我们在使用的时候的形势其实注定了,上面的①中的this其实就是a的地址.
而下面的②其实this也是a的地址.
两者在编译看来没有区别.编译器也会2变成1后,然后编译.
如果有两个变量一般是左边的左this指针,三个变量就是最左的是this指针
类比思考一下,我们的前置后置++ -- 等其实在类内实现的时候就没必要专门设置形参只需要一个this指针就够了.但是这样就没办法分别了,所以我们规定后置类型要创建一个int 类型的形参用于函数分辨.以便形成重构.
下面是对日期类所有操作符重载的实现,我们在实现的时候,有些部分可以操作符可以复用最好复用,复用带来的好处有很多,不仅方便,而且后期找bug也可以减少低级错误.
class Date
{
public:
void Print()
{
cout << _year << "_" << _month << "_" << _day << endl;
}
int GetMonthDay(int year, int month)
{
if (month > 12)
{
cout << "月输入错误" << endl;
return -1;
}
int const arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && JudgeLeapYear(year))
{
return 29;
}
return arr[month];
}
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
~Date()
{
;
}
Date& operator+=(int day)
{
*this = *this + day;
return *this;
}
Date operator+(int day)
{
Date tmp(*this);
(tmp._day) += day;
while (tmp._day >= GetMonthDay(tmp._year, tmp._month))
{
tmp._day -= GetMonthDay(tmp._year, tmp._month);
tmp._month += 1;
if (tmp._month == 13)
{
tmp._month -= 12;
tmp._year += 1;
}
}
return tmp;
}
Date operator-(int day)
{
Date tmp(*this);
(tmp._day) -= day;
while (tmp._day <= 0)
{
tmp._day += GetMonthDay(tmp._year, tmp._month);
tmp._month -= 1;
if (tmp._month == 0)
{
tmp._month += 12;
tmp._year -= 1;
}
}
return tmp;
}
Date& operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
_day += GetMonthDay(_year, _month);
_month -= 1;
if (_month == 0)
{
_month = 12;
_year -= 1;
}
}
return *this;
}
Date& operator++()
{
*this += 1;
return *this;
}
Date operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
Date operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
Date& operator--()
{
*this -= 1;
return *this;
}
bool operator>(const Date& d)
{
if (_year > d._year || (_year == d._year && _month > d._month) || (_year == d._year && _month == d._month && _day > d._day))
{
return true;
}
else
{
return false;
}
}
bool operator==(const Date& d)
{
if (_year == d._year && _month == d._month && _day == d._day)
{
return true;
}
else
{
return false;
}
}
inline bool operator >= (const Date& d)
{
if (*this > d || *this == d)
{
return true;
}
else
{
return false;
}
}
bool operator < (const Date& d)
{
return !(*this >= d);
}
bool operator <= (const Date& d)
{
if (*this < d || *this == d)
{
return true;
}
else
{
return false;
}
}
bool operator != (const Date& d)
{
return !(*this == d);
}
int operator-(const Date& d)
{
Date min = (*this > d ? d : *this);
Date max = (*this > d ? *this : d);
int ret = 0;
while (min != max)
{
min++;
ret++;
}
return ret;
}
private:
bool JudgeLeapYear(int year)
{
if ((year % 4 == 0 && year % 100 != 0) || year % 100 == 0)
{
return true;
}
else
{
return false;
}
}
private:
int _year;
int _month;
int _day;
};
还有&的重载但是这个一般使用编译器自动生成的就好.格式和前面的一样.
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ;
int _month ;
int _day ;
};
值得提一嘴的是,我们const修饰指针的时候也是可以组成函数重载的,被const修饰的指针在C++的命名规则下会加上一个K.
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)
{
}
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()
{
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类里的私有成员.
注:
内部类
概念: 一个类定义在另一个类的内部就叫内部类.
注意: 内部类是外部类的友元类但是外部类对内部类没有任何优先和权力,对于外部类来说内部类就跟普通的类一样,外部类没有内部类的任何特权.
并且内部类可以不通过对象等做到直接访问外部类的成员(包括:枚举成员(就是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也是符合的
|