目录
一、认识类;
1、类的引入;
2、类的定义;
类的两种定义方式:
3、类的访问限定符及封装;
4、类的作用域;
5、类的实例化;
6、类对象模型;
计算类对象的大小;
代码:
运行结果:
结果分析:
类对象的存储方式;
结构体内存对齐规则;
7、this指针;
代码:
运行结果:
结果分析:
代码:
运行结果:
代码分析:
二、类的默认成员函数和成员;
1、构造函数;
定义:
特点:
2、析构函数;
概念:
特性:
代码:
运行结果:
结果分析:
3、拷贝构造函数;
概念:
特征:
代码:
运行结果:
4、赋值运算符重载;
概念:
代码(以date类举例):
运行结果:
编辑
结果分析:
+=运算符和+运算符;
代码:
代码分析:
+=负数;
代码:
代码分析:
>>和<<运算符;
代码:
代码分析:
前置++和后置++运算符;
代码:
代码分析:
赋值运算符重载
赋值运算符重载格式
代码:
初始化列表
代码:
代码分析:
参数隐式类型转换;
单参数;
代码:
代码分析:
运行结果:
C++11支持多参数隐式类型转换;
代码:
代码分析:
运行结果:
explicit:
static;
概念:
特性:
代码:
代码分析:
运行结果:
友元;
友元函数:
说明:
代码:
代码分析:
友元类:
代码:
代码分析:
内部类;
概念:
特性:
代码:
代码分析:
运行结果:
一、认识类;
1、类的引入;
C 语言结构体中只能定义变量,在 C++ 中,结构体内不仅可以定义变量,也可以定义函数。 比如:之前在数据结构初阶中,用C 语言方式实现的栈,结构体中只能定义变量 ;现在以 C++ 方式实现, 会发现 struct中也可以定义函数。C++兼容C语言;结构可以继续使用;结构体升级成了类;
struct stack//还可以定义成员函数;
{
void init()//在里面写可以省略很多参数;
{
a = nullptr;
size = 0;
capa = 0;
}
void push(int x)
{
}
int* a;
int size;
int capa;
};
int main()
{
stack s1;
s1.init();
s1.push(1);
struct stack s2;
s2.init();
s2.push(2);
return 0;
}
2、类的定义;
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分 号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。
class className
{
类体:由成员函数和成员变量组成
};
类的两种定义方式:
声明和定义全部放在类体中,需注意:成员函数如果 在类中定义 ,编译器可能会将其当成 内联函数 处理。类声明放在 .h 文件中,成员函数定义放在 .cpp 文件中,注意: 成员函数名前需要加类名 ::;
3、类的访问限定符及封装;
public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的);访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止;如果后面没有访问限定符,作用域就到 } 即类结束。class的默认访问权限为private,struct为public(因为struct要兼容C);类里面不受访问限定符的限制,访问限定符限制的是外面的人使用;
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来 和对象进行交互。
class stack//还可以定义成员函数; //类里面不受访问限定符的限制,访问限定符限制的是外面的人使用;
{
public://类一开始默认为私有的;struct一开始默认为公有的;
void init()//在里面写可以省略很多参数;
{
a = nullptr;
size = 0;
capa = 0;
}
void push(int x)
{
if (size == capa)
{
size_t newcapa = capa == 0 ? 4 : 2 * capa;
a =(int*) realloc(a,newcapa * (sizeof(int)));
if (a == nullptr)
{
return;
}
capa = newcapa;
}
a[size++] = x;
}
int top()//栈顶元素;
{
return a[size - 1];
}
void destroy()
{
free(a);
a = nullptr;
size = capa = 0;
}
private://在外面不能访问;访问限定符范围是到下一个访问限定符、
int* a;
int size;//
int capa;
};
int main()
{
stack a;
a.init();
a.push(1);
a.push(2);
a.push(3);
a.push(4);
/*cout << a.size << endl;
cout << a.a[a.size-1] << endl;*///
cout << a.top() << endl;
return 0;
}
4、类的作用域;
类定义了一个新的作用域 ,类的所有成员都在类的作用域中 。 在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。类可以声明和定义分离;
class stack//还可以定义成员函数; //类里面不受访问限定符的限制,访问限定符限制的是外面的人使用;
{//类是一个整体,没有什么向上向下找;
public://类一开始默认为私有的;struct一开始默认为公有的;
void init();//在里面写可以省略很多参数;
void push(int x);//函数在类里面直接定义,默认为内联;
int top();//栈顶元素;
void destroy();
private://在外面不能访问;访问限定符范围是到下一个访问限定符、
int* a;
int size;//
int capa;
};
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:6031)
#include"stack.h"
void stack::init()//只有加了::后函数里面才能访问私有变量;默认去全局找,加了限定符就可以去找;
{
a = nullptr;
size = 0;
capa = 0;
}
void stack::push(int x)
{
if (size == capa)
{
size_t newcapa = capa == 0 ? 4 : 2 * capa;
a = (int*)realloc(a, newcapa * (sizeof(int)));
if (a == nullptr)
{
return;
}
capa = newcapa;
}
a[size++] = x;
}
int stack::top()//栈顶元素;
{
return a[size - 1];
}
void stack::destroy()
{
free(a);
a = nullptr;
size = capa = 0;
}
5、类的实例化;
用类类型创建对象的过程,称为类的实例化;声明,没有定义;声明和定义的区别:声明没有创造空间,定义创造空间;类存在文件系统里,不占内存的空间;(类似于建房的图纸);类实例化出来才是建的房子;类要定义一个具体变量后才能使用;函数类似于小区 的篮球场游泳馆等公共设施,存在公共代码区(全局函数也存在里面)(不在对象里);变量类似于每一栋小房子;
6、类对象模型;
计算类对象的大小;
代码:
class A1 {
public:
void f1() {}
private:
int _a;
};
class A2 {
public:
void f2() {}
};
// 类中什么都没有---空类
class A3
{};
int main()
{
cout << sizeof(A1) << endl;
cout << sizeof(A2) << endl;
cout << sizeof(A3) << endl;
return 0;
}
运行结果:
结果分析:
函数类似于小区 的篮球场游泳馆等公共设施,存在公共代码区(全局函数也存在里面)(不在对象里);变量类似于每一栋小房子;所以类的大小就是变量存储的空间大小;有变量就按结构体内存对齐原则计算大小;没有变量大小就是1,内存是1是因为要分配一个字节占位,表示同类对象的区别,仅仅表示对象存在过(定义出来);
类对象的存储方式;
一个类的大小,实际就是该类中 ” 成员变量 ” 之和,当然要注意内存对齐; 注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
结构体内存对齐规则;
1. 第一个成员在与结构体偏移量为0的地址处。2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的对齐数为83. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。问:为什么要内存对齐?
答:cpu去访问内存的时候,一般不是从任意位置访问,一般依次访问32位,例如四字节四字节访问;如果不内存对齐,访问一个数据可以要读多次,读多次会有多余变量,有可能涉及到数据拼接问题;可以通过program 来改变默认对齐数;
7、this指针;
C++ 编译器给每个 “ 非静态的成员函数 “ 增加了一个隐藏 的指针参数,让该指针指向当前对象 ( 函数运行时调用该函数的对象 ) ,在函数体中所有 “ 成员变量 ” 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编 译器自动完成 。1. this 指针的类型:类类型 * const ,即成员函数中,不能给 this 指针赋值。2. 只能在 “ 成员函数 ” 的内部使用。3. this 指针本质上是 “ 成员函数 ” 的形参 ,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以 对象中不存储 this 指针 。4. this 指针是 “ 成员函数 ” 第一个隐含的指针形参,一般情况由编译器通过 ecx 寄存器自动传递,不需要用户传递。
代码:
class date
{
public:
void init(int year, int month, int day)
{ //this指针不可以在形参和实参显式的写,但是可以在类里面显示的用
_year = year;//this->_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
void printpos()
{
cout << this << endl;
}
private://类只存变量;
int _year;//加_是为了好区分;
int _month;
int _day;
};
int main()
{
date d1;
d1.init(1, 2, 3);//d1.init(&d1,1, 2, 3);
cout << &d1 << endl;
d1.printpos();
return 0;
}
运行结果:
结果分析:
init函数实际上是void init(date* this,int year, int month, int day)编译器加上this指针数;this指针就是定义的类的地址;this指针不可以在实参和形参显式的写,但是可以显式调用;、
代码:
class a
{
public:
void print()
{
cout << "print()" << endl;
}
private:
int _a;
};
int main()
{
/*a* p = nullptr;
p->print();*///p对象成员函数的地址不存在对象里,而是存在代码区,所以直接那函数名字地址去代码区找,所以没有对p解引用;
a* p = nullptr;
(*p).print();//print依旧不在对象里,所以依旧不会对p解引用; //只有光写p->a不会崩;p->a=1会崩;
return 0;
}
运行结果:
代码分析:
p对象成员函数的地址不存在对象里,而是存在代码区,所以直接那函数名字地址去代码区找,所以没有对p解引用;是否在p解引用看访问的成员是否在p里面;
二、类的默认成员函数和成员;
1、构造函数;
定义:
构造函数是一个特殊的成员函数,名字与类名相同(date类构造函数 date(x,y)),创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
特点:
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
1. 函数名与类名相同。2. 无返回值。3. 对象实例化时编译器 自动调用 对应的构造函数。4. 构造函数可以重载。
date()
{
cout << "date()" << endl;
_year = 1;
_month = 1;
_day = 1;
}
//带参初始化;
date(int year, int month, int day)
{
cout << "date(int year, int month, int day)" << endl;
_year = year;
_month = month;
_day = day;
}
上面两个构造合并;
date(int year=1, int month=1, int day=1)//
{
cout << "date(int year=1, int month=1, int day=1)" << endl;
_year = year;
_month = month;
_day = day;
}
注意:如果通过无参构造函数创建对象时,对象后面不用跟括号(date d1),否则就成了函数声明(date d1());第一个和第三个语法上可以同时存在 ,但是使用的时候不可以;如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。默认构造的特点:
1、我们写它生成,我们写了它不生成;
2、内置类型:语言原生的类型(指针都是内置类型);自定义:类、结构;C++默认构造对内置类型不处理、处理自定义类型(C++支持声明给缺省值),会去调用这个成员的默认构造函数;什么是默认构造:无参的构造函数、全缺省的构造函数、我们不写编译器自动生成的构造函数都被称为默认构造函数(不传参就会调用),他们只能有一个;
2、析构函数;
概念:
通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
特性:
析构函数 是特殊的成员函数,其 特征 如下:1. 析构函数名是在类名前加上字符 ~ 。2. 无参数无返回值类型。3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载。4. 对象生命周期结束时, C++ 编译系统系统自动调用析构函数。(例如date类的析构函数是~date())。5、如果系统没有显示定义,会生成默认析构,对于内置类型不处理,自定义类型调用它的析构函数处理。
代码:
class x
{
public:
x(int a=1)
{
cout << "x()" << endl;
_a = a;
}
~x()
{
cout << "~x()" << endl;
}
private:
int _a;
};
int main()
{
x x1;
return 0;
}
运行结果:
结果分析:
构造和析构的最大特性是自动调用。
3、拷贝构造函数;
概念:
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象创建新对象时由编译器自动调用。
特征:
拷贝构造函数也是特殊的成员函数,其 特征 如下:1. 拷贝构造函数 是构造函数的一个重载形式 。2. 拷贝构造函数的 参数只有一个 且 必须是类类型对象的引用 ,使用 传值方式编译器直接报错 , 因为会引发无穷递归调用。(如果是date(date d),那么d1(d2)会先d(d2),形成了新的拷贝构造,导致又要 d3(d2),无穷调用)。若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝,会造成资源二次释放的问题。代码:
class stack//还可以定义成员函数; //类里面不受访问限定符的限制,访问限定符限制的是外面的人使用; { public://类一开始默认为私有的;struct一开始默认为公有的; stack(int n = 4) { cout << "stack()" << endl; cout << "stack(int n = 4)" << endl; if (n == 0) { a = (int*)malloc(sizeof(int) * 4); if (a == nullptr) { perror("malloc fail"); } size = 0; capa = 4; } else { a = (int*)malloc(sizeof(int) * n); if (a == nullptr) { perror("malloc fail"); } size = 0; capa = n; } } void push(int x) { if (size == capa) { size_t newcapa = capa == 0 ? 4 : 2 * capa; auto x1 = a; a = (int*)realloc(a, newcapa * (sizeof(int))); if (a == nullptr) { perror("realloc fail"); exit(-1); } capa = newcapa; } a[size++] = x; } void pop() { assert(size > 0); size--; } int top()//栈顶元素; { return a[size - 1]; } void print() { cout << size << endl << capa << endl; } stack(const stack& s)//解决了两次析构问题;(深拷贝); { //s不能改变;防止错误代码; cout << "stack(stack& s)" << endl; a= (int*)malloc(s.capa * (sizeof(int))); if (a == nullptr) { perror("realloc fail"); exit(-1); } memcpy(a, s.a,sizeof(int)*s.size); size = s.size; capa = s.capa; } ~stack() { cout << "~stack()" << endl; free(a); a=nullptr;//这行和下一行不写不算错,算是好习惯; size = capa = 0; } bool isempty() { //assert(size>0);// return size == 0; } private: int* a; int size ; int capa ; }; class date { public: date(int year=2024, int month=11, int day=11) { year_ = year; month_ = month; day_ = day; } void print() { cout << year_<<"/" << month_ << "/" << day_ << endl; } // date d2(d1); d是d1的别名,this是d2的指针; date(const date& d)//如果是date(date d),那么d1(d2)会先d(d2),形成了新的拷贝构造,导致又要 d3(d2),无穷调用; { //防止错误的改变,防止误伤; cout << "date(date& d)" << endl; year_ = d.year_; month_ = d.month_; day_ = d.day_; } ~date() { cout << "~date()" << endl; } private: int year_; int month_; int day_; }; void func1(date d) { d.print(); } void func2(stack& s)//s的作用域是main函数栈帧,但是问题是s变化s1也变化; { s.push(1); s.push(2); } int main() { date d1; func1(d1); stack s1; func2(s1);//会出现浅拷贝问题 stack s2(s1); //以下两种写法一样; date d2(d1); date d3 = d1; return 0; }
运行结果:
4、赋值运算符重载;
概念:
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。函数名字为:关键字 operator 后面接需要重载的运算符符号 。函数原型: 返回值类型 operator 操作符 ( 参数列表 )注意:不能通过连接其他符号来创建新的操作符:比如 operator@重载操作符必须有一个类类型参数用于内置类型的运算符,其含义不能改变,例如:内置的整型 + ,不 能改变其含义作为类成员函数重载时,其形参看起来比操作数数目少 1 ,因为成员函数的第一个参数为隐藏的 this.* :: sizeof ?: . 注意以上 5 个运算符不能重载。这个经常在笔试选择题中出现。
代码(以date类举例):
class date
{
public:
date(int year=2024, int month=11, int day=11)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year<<"/" << _month << "/" << _day << endl;
}
date(const date& d);
{
cout << "date(date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
bool operator<( const date& x2)//运算符重载可以写为全局函数,也可以写为类里面的成员函数;
{
if (_year < x2._year)
{
return true;
}
else if (_year == x2._year && _month < x2._month)
{
return true;
}
else if (_year == x2._year && _month == x2._month && _day < x2._day)
{
return true;
}
else
{
return false;
}
}
bool operator==(const date& x2)
{
return _year == x2._year && _month == x2._month && _day == x2._day;
}
bool operator<=(const date& x2)
{
return ((*this)<x2)|| ((*this) == x2);
}
bool operator>(const date& x2)
{
return !((*this) <= x2);
}
bool operator!=(const date& x2)
{
return !((*this) == x2);
}
~date()
{
cout << "~date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date d1(2023, 11, 11);
date d2(2024, 12, 12);
//为了对自定义类型进行比较,发明了运算符重载;
int i1 = 10;
int j1 = 100;
i1 < j1;//内置类型转为运算符指令;
cout << (d1< d2) << endl;//自定义类型转为运算符重载;
//cout << (operator<(d2, d1) )<< endl;//可以显示调用;
cout << (d1 < d2) << endl;//上面的自动转为下列公式;
cout << d1.operator<(d2) << endl;
cout << (d2 < d1) << endl;
cout << d2.operator<(d1) << endl;
cout << (d1 == d2)<<endl;
cout << (d1 != d2) << endl;
cout << (d1 <= d2) << endl;
cout << (d1 > d2) << endl;
return 0;
}
运行结果:
结果分析:
自定义类型原来不支持比较,写了运算符重载才支持;运算符重载不能改变操作符的操作数个数;写完运算符重载后可以显式调用也可以隐式调用。注意比较的时候操作数的左右顺序要和重载函数的顺序一致。
+=运算符和+运算符;
+=原来的数改变;+原来的数不改变;
代码:
int getmonthday(int year,int month)//得到每年每月的天数。
{
int monthday[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2&&((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
{//先判断是不是2月;
monthday[2]+=1;
}
return monthday[month];
}
date& operator+=(int day)
{
_day += day;
while (_day > getmonthday(_year, _month))//多次循环,直到天数小于该月的天数;
{
_day -= getmonthday(_year, _month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
//+=的时候返回的是*this,出了作用域还在,可以用引用返回;+返回的是d2,出了作用域就不在了,使用传值返回;
date operator+(int day)
{
date d2(*this);//不写这个是+=
//d2._day += day;
//while (d2._day > getmonthday(d2._year, d2._month))//多次循环,直到天数小于该月的天数;
//{
// d2._day -= getmonthday(d2._year,d2._month);
// d2._month++;
// if (d2._month > 12)
// {
// d2._year++;
// d2._month = 1;
// }
//}
//return d2;
d2 += day;
return d2;
}
代码分析:
+可以通过拷贝构造,然后复用+=;+=的时候返回的是*this,出了作用域还在,可以用引用返回;+返回的是d2,出了作用域就不在了,使用传值返回;
+=负数;
代码:
date& date::operator-=(int day)
{
if (day < 0)
{
return *this += (-day);
}
_day -= day;
while (_day < 1)
{
_month--;
if (_month < 1)
{
_year--;
_month = 12;
}
_day += getmonthday(_year, _month);
}
return *this;
}
date& date::operator+=(int day)
{
if (day < 0)
{
return *this -= (-day);
}
_day += day;
while (_day > getmonthday(_year, _month))
{
_day -= getmonthday(_year, _month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
代码分析:
+=负值等于-=正值;
>>和<<运算符;
代码:
ostream& operator<<(ostream& out, date& d)//<< 可以更好的打印自定义类型;
{
out << d._year << "/" << d._month << "/" << d._day << endl;
return out;//为了多个一块打印;
}
istream& operator>>(istream& in, date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
代码分析:
由于cout<<d;打印的顺序d在后,所以不能将<<重载函数写在类里面;只能写在类外;由于cout<<d1<<d2,打印完d1之后还要打印d2,所以返回值是out。
前置++和后置++运算符;
代码:
date& date::operator++()
{
*this += 1;
return *this;
}
date date::date::operator++(int)//int 只是为了匹配;
{
date d(*this);
*this += 1;
return d;
}
代码分析:
后置++和前置++最后都是加1,但是返回值不同;
赋值运算符重载
赋值运算符重载格式
参数类型 : const T& ,传递引用可以提高传参效率返回值类型 : T& ,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值检测是否自己给自己赋值返回 *this :要复合连续赋值的含义赋值重载如果没有显示定义;默认赋值重载函数:内置类型值拷贝,自定义类型调用赋值重载函数;
代码:
Date& operator=(const Date& d)
{
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
初始化列表
初始化列表:以一个 冒号开始 ,接着是一个以 逗号分隔的数据成员列表 ,每个 " 成员变量 " 后面跟一个 放在括 号中的初始值或表达式。
代码:
class date
{
public:
date(int year, int month, int day, int x, int& rex)
: _year(year)
, _month(month)
, _rex(rex)
, _x(x)//这就是定义的地方,在此位置初始化;//如果给了缺省值,这行不写也行(这行写的原因是给值,如果有缺省,可以不用写)
, a1(1)//没有默认构造,在定义的地方初始化;
{//括号上面是定义的地方(初始化列表); //先走上面,再走函数体;所有成员都在上面定义
//_x = x;//const这个位置不行
_day = day;//普通成员可以在里面定义(因为普通成员不需要在定义的地方初始化);
}
void func1()
{
++_rex;
}
private://支持给缺省值;缺省值给初始化列表;如果初始化列表没有显示给值,就用缺省值;显式给值,不用缺省值;
int _year;//每个成员的声明(不开空间)
int _month;
int _day;
//成员必须在定义的时候初始化
const int _x;//const 成员必须在定义的时候初始化,以后不能修改;
int& _rex;//引用也必须在定义的时候初始化;
A a1;
};
代码分析:
初始化列表;每个成员定义的地方;(开空间);(初始化列表是构造函数的一部分);初始化列表是成员定义的地方,不管写不写,都处理;初始化列表在没有写的一些变量,内置类型随机值,自定义类型调用默认构造;初始化列表初始化的顺序是按声明的顺序;1. 每个成员变量在初始化列表中 只能出现一次 ( 初始化只能初始化一次 )2. 类中包含以下成员,必须放在初始化列表位置进行初始化:引用成员变量;const 成员变量;自定义类型成员 ( 且该类没有默认构造函数时 );
参数隐式类型转换;
单参数;
代码:
class A
{
public:
/*explicit*/ A(int a)//explicit 可以禁止单参数隐式类型转换
:_a(a)
{
cout << "A(int a)" << endl;
}
A(const A& a)
:_a(a._a)
{
cout <<"A(const A& a)"<< endl;
}
~A()
{
cout << "~A" << endl;
}
private:
int _a;
};
int main()
{
A a(1);
A b = 2;
const A& x= 2;
return 0;
}
代码分析:
单参数隐式类型转换(传参可以直接传参数值,自动转为类);c++支持单参数隐式类型转换,用2调用a 构造函数创造临时对象,再用这个对象拷贝构造给b,后来创建对象时候,c++后来优化,不先构造对象,而是用2直接创造对象;x这里不能优化,用2调用a 构造函数创造临时对象,临时对象具有常性,权限不能放大,引用要加const;
运行结果:
C++11支持多参数隐式类型转换;
代码:
class B
{
public:
/*explicit*/ B(int b1,int b2) //explicit 可以不允许隐式类型转换发生
:_b1(b1)
,_b2(b2)
{
cout << "B(int b1,int b2)" << endl;
}
B(const B& b)
:_b1(b._b1)
,_b2(b._b2)
{
cout << "B(const B& b)" << endl;
}
private:
int _b1;
int _b2;
};
int main()
{
B b(1, 2);
B b1 = { 1,2 }
const B& c = { 1,2 };
return 0;
}
代码分析:
与单参数类似,传参就能自动构造类,连续的构造拷贝构造可以省略;多参数隐式类型转换,用1,2调用b 构造函数创造临时对象,再用这个对象拷贝构造给b;引用的时候注意临时对象具有常性。
运行结果:
explicit:
用 explicit 修饰构造函数,将会禁止构造函数的隐式转换 。
static;
概念:
声明为 static 的类成员 称为 类的静态成员 ,用 static 修饰的 成员变量 ,称之为 静态成员变量 ;用 static 修饰 的 成员函数 ,称之为 静态成员函数 。 静态成员变量一定要在类外进行初始化。
特性:
1. 静态成员 为 所有类对象所共享 ,不属于某个具体的对象,存放在静态区。2. 静态成员变量 必须在 类外定义 ,定义时不添加 static 关键字,类中只是声明。3. 类静态成员即可用 类名 :: 静态成员 或者 对象 . 静态成员 来访问。4. 静态成员函数 没有 隐藏的 this 指针 ,不能访问任何非静态成员。5. 静态成员也是类的成员,受 public 、 protected 、 private 访问限定符的限制。
代码:
class A
{
public:
A()
{
n++;
m++;
}
A(const A& a)
{
int x = getm();
n++;
m++;
}
//必须要创建新的对象才能调用print函数;
static int getm()
//getm只能读不能改;
{
/*x++;*///静态成员函数不能访问非静态成员变量,因为没有this指针;
return m;
}
static void print()//加了static,表示函数体内没有this指针;
{
cout << m << " " << n << endl;
}
~A()
{
m--;
}
private:
//static int n = 0;//加了static就是全部类用的(存在静态区),属于所有对象,属于类域;//这里不能给缺省值;因为初始化列表是给某对象用的;static不是某个对象的变量,所有不走初始化列表;
//static int m = 0;
static int n;//static成员变量不是私有成员变量,所以不需要this指针,如果是公有的话,只需要突破类域;
static int m;//在外面定义;
int x;
};
int A::m = 0;
int A::n = 0;
int main()
{
A aa1;
A aa2;
A();
A();
//cout << "目前创建:" << A::n << " " << "目前使用:" << A::m << endl;//如果成员变量私有化,不能这样访问;
A::print();//print()是static函数,没有this指针,可以通过类域,变量名,变量指针调用;
aa1.print();
A* x = nullptr;//因为调用函数不需要this指针,只需要突破类域;
x->print();
aa1.print();
}
代码分析:
m、n是static变量,要在外面定义;但是受访问限定符限制,所以类外面不能直接访问;print是static函数,因为静态成员函数没有this指针,且是公有,所以只需要突破类域就能访问;突破类域有三种方式:域访问限定符(::)、指针->、类变量。
运行结果:
友元;
友元函数:
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。有在类外面访问类成员的需求;(>>、<<重载函数);(定义私有成员的目的就是不被随便访问,所以最好不要定义太多友元函数);友元关系不具有传递性;(不到万不得已不要定义友元)。
说明:
友元函数 可访问类的私有和保护成员,但 不是类的成员函数。友元函数 不能用 const 修饰。友元函数 可以在类定义的任何。地方声明, 不受类访问限定符限制。一个函数可以是多个类的友元函数。友元函数的调用与普通函数的调用原理相同。
代码:
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
_cin >> d._year;
_cin >> d._month;
_cin >> d._day;
return _cin;
}
代码分析:
>>和<<是date的友元函数,所以可以访问私有成员变量。
友元类:
友元关系是单向的,不具有交换性。类的私有成员变量,但想在 Time 类中访问 Date 类中私有的成员变量则不行。友元关系不能传递如果 B 是 A 的友元, C 是 B 的友元,则不能说明 C 时 A 的友元。
代码:
class Time
{
friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成,但是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;
};
int main()
{
return 0;
}
代码分析:
上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time。反过来却不行。
内部类;
概念:
如果一个类定义在另一个类的内部,这个内部类就叫做内部类 。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。注意:内部类就是外部类的友元类 ,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
1. 内部类可以定义在外部类的 public 、 protected 、 private 都是可以的。2. 注意内部类可以直接访问外部类中的 static 成员,不需要外部类的对象 / 类名。3. sizeof( 外部类 )= 外部类,和内部类没有任何关系。内部类类受外部类类域和访问限定符的限制,本身是两个独立的类。
代码:
class A
{
public:
class B//
{ //内部类是外部类的友元;内部类可以通过变量名+.访问私有成员变量;
public:
void func()
{
A a;
a._a = 10;//内部类是外部类的友元;内部类可以通过变量名+.访问私有成员变量;
}
private:
int _b;
};
private:
int _a;
};
int main()
{
cout << sizeof(A) << endl;
A aa1;
A::B bb1;
return 0;
}
代码分析:
B类受A类类域和访问限定符的限制,本身是两个独立的类;sizeof( 外部类 )= 外部类。