1:类的结果以及类的部分定义与性质
2:六种默认成员函数
//////
1.1:类的结构:
在C语言我们以struct来定义结构体,结构体内可定义变量,但是不能定义函数,在c++中我们定义以class为定义类的关键字,{}内为类的主体,与C语言结构体的样式类似,但是类中不仅可以定义变量,也能定义函数,同时,注意类定义结束时后面分号不能省略。具体形式如下所示:、
class classname//class表示类,classname表示类的名称
{
//成员变量及成员函数;
};//分号不能省
//////
1.2:类的部分定义:
类的成员:类体中内容称为类的成员:类中的变量称为类的属性或成员变量;
类中的函数称为类的方法或者成员函数;
类的定义:
第一种:声明与定义全在类中,(定义在类中的成员函数有可能会被当作内联函数展开)
class calssname
{
void my_print()//成员函数声明与定义实现都在类中
{
cout << "classname" << endl;
}
int _a;//成员变量也定义在类中;
int _b;
};
第二种:声明在类中,定义在类外,此时,调用时需要指明类域(类名)
class calssname
{
void my_print();//成员函数只有声明在类中
int _a;
int _b;
};
void calssname::my_print()//成员函数定义实现不在类中,所以要指定类域;
{
cout << "calssname" << endl;
}
一般使用第二种类定义(声明与结构分离)
//////
1.3:类的部分性质:
1.3.1:访问限定符:为了使外部不能随意访问类中信息,c++提供了访问限定符(以struct,class为例)
除了类定义使用的关键字不同,其余都相同,但是关键字导致产生了不同的结果,所以由此明白:calss所定义的类其默认权限是私有的,而struct所定义的类其默认权限是公有的,
访问限定符有三种:public(公有),private(私有),protected(保护)
有以下的性质:
1. public修饰的成员在类外可以直接被访问
2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4. 如果后面没有访问限定符,作用域就到 } 即类结束。
5. class的默认访问权限为private,struct为public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
///
///
1.3.2:类的作用域与实例化:
1.作用域:类定义了一个作用域。在类外想要调用类中成员,要加上(域作用符号::)指定成员属于那个类作用域;
2.实例化:类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;只有实例化出的对象 占用实际的物理空间,才存储类成员变量
///
///
1.3.3:类对象的大小计算
成员函数计算:
类是面向对象的一种模型,不同的成员变量就有不同对象,但是类中的成员函数却被经常调用,不同的对象调用同一份的成员函数,为了防止空间的浪费,我们将成员函数放入到公共代码段中;
不参与类对象大小的计算;
成员变量计算:
就如C语言中求结构体大小一样,成员变量我们按照结构体的计算方式(结构体对齐)的方式算,
类的大小主要取决于成员变量;
[N]:对于空类来说,没有成员变量,但是编译器会给空类一个字节来唯一标识这个类的对象;
///
///
1.3.4:*this使用与特性
1.*this使用
class date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
cout << this << endl;
}
void print()
{
cout << _year << ":" << _month << ":" << _day << endl;
}
void fun()
{
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date d1;
date d2;
d1.Init(2023, 2, 20);
d1.print();
d2.Init(2022, 2, 20);
d2.print();
在这段代码中,实例化两个对象,同时调用Init函数,但是d1,d2是相同的类型,编译器是如何分辨到底是d1,还是d2在调用Init函数呢?
每一个实例化的对象在调用成员函数时,编译器会自己传过去一个指针,指向这个实例化对象;
以Init(int year, int month, int day)为例,当实例化对象调用时,编译器会传过去一个指针*this,此时,Init函数变成了Init(date*this,int year, int month, int day);但是,这是编译器隐式做的,我们不需要在调用时显示调用
2.*this特性、
1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
2. 只能在“成员函数”的内部使用
3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给
this形参。所以对象中不存储this指针。
4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传
递,不需要用户传递
//////
///
2:六种默认成员函数
主要是第一到第四种,其他很少自己实现:
/////
2.1构造函数
2.1.1.定义:构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次;
如果自己在类中实现了构造函数,则不会去调用编译器生成的构造函数;
//
2.1.2.特性:构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载
class date
{
public:
//date()//无参构造函数
//{
// _year = 2023;
// _month = _day = 2;
//}
//date(int year, int month, int day)//有参数构造函数
//{
// _year = year;
// _month = month;
// _day = day;
//}
date(int year=1, int month=2, int day=3)//全缺省构造函数;
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << ":" << _month << ":" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date d1;
d1.print();
date d2(2023, 2, 20);
d2.print();
date d3(2023,4);
d3.print();
return 0;
}
[N]:要么自己不写构造函数,使用编译器自动生成的构造函数;
要么自己写一个全缺省的构造函数,调用自己的构造函数;
要么自己写一个无参构造函数和有参构造函数构成函数重载,
三者选其一
//
2.1.3.对自定义类型和内置类型的处理
内置类型:如int ,char ,double,等语言提供的数据类型;
自定义类型:自定义类型就是我们使用class/struct/union等自己定义的类型;
构造函数对内置类型的处理:默认生成的构造函数对内置类型不做处理;调用构造时会将内置类型初始化为随机值
//默认生成的构造函数与析构函数对内置类型不做处理;调用构造时会将内置类型初始化为随机值;
class date
{
public:
void print()
{
cout << _year << ":" << _month << ":" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date d1;
d1.print();
return 0;
}
//
构造函数对自定义类型的处理:默认生成的构造函数对自定义类型做处理;调用构造时会调用自己的自定义类型(stack)的默认构造函数;
//默认生成的构造函数与析构函数对自定义类型做处理;调用构造时会调用自己的自定义类型(stack)的默认构造函数;
class stack
{
public:
stack()
{
_a = nullptr;
_size = _capacity = 0;
cout << "stack()" << endl;
}
private:
int* _a;
int _size;
int _capacity;
};
class MYQueue
{
public:
void push(int x)
{
}
//.....
//.....
private:
stack _pushst;
stack _popst;
};
int main()
{
MYQueue q;//会调用stack的构造函数,因此MYQeue不需要构造函数也可以完成初始化;
return 0;
}
[N]:既然构造函数只能将自定义类型初始化成功,不能将内置类型初始化成功,所以c++11中对构造函数不能成功初始化内置类型打了补丁,即内置类型在声明时可以给默认缺省值;
//解决默认构造函数不出来内置类型的方法:给内置类型缺省值;
class date
{
public:
void print()
{
cout << _year << ":" << _month << ":" << _day << endl;
}
private:
//给缺省值,调用默认函数时会用缺省值初始化;
int _year=1;
int _month=1;
int _day=1;
};
int main()
{
date d1;
d1.print();
return 0;
}
//
2.1.4.构造函数初始化列表
构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始
化一次,而构造函数体内可以多次赋值,同时,还有一些特殊的类型需要初始化,比如引用,const成员变量等;因此为解决此类问题,引入初始化列表
1.形式:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟
一个放在括号中的初始值或表达式。
//初始化列表:
//三种必须在初始化列表初始化的成员变量:
//const,&(引用),自定义类型(date)(没有无参的默认构造函数);
class MKL
{
public:
MKL()//初始化列表
:_x(1)
, _a1(2)
,_a2(2)
, b(0)
{
_a1++;
_a2--;
}
private:
int _a1;
int _a2;
const int _x;//const修饰的成员变量
date b;//自定义类型
};
2.顺序:初始化列表初始化顺序按照 类里 声明的成员变量顺序来初始化;
//初始化列表初始化顺序按照 类里 声明的成员变量顺序来初始化;
class MKL
{
public:
MKL(int t)//因为声明时_a1在_a2之前,所以初始化的时候_a1初始化在_a2之前
:_a2(t)
,_a1(_a2)//先初始化_a1,但是_a2还没有初始化,是随机值,如果拿_a2来初始化_a1,则_a1也为随机值;
{
cout << "MKL()" << endl;
cout << _a1 << endl;
cout << _a2 << endl;
/*cout << _x << endl;*/
}
private:
int _a1 =0;
int _a2 = 1;
//const int _x;
};
int main()
{
MKL aa(1);
return 0;
}
/////
2.2析构函数
2.2.1.定义:与类名相反(类名前加'~')与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
///
2.2.2.特性:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
5.先创建的对象后析构,后创建的对象先析构;
/////
2.3拷贝构造函数
2.3.1.定义:拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
///
2.3.2.特性:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,
因为会引发无穷递归调用
3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
2.3.3.深浅拷贝:
浅拷贝:当自己不定义拷贝构造时,编译器会给出一个默认拷贝构造,但是此拷贝构造遵循一个字节一个字节的拷贝,但是碰见栈,堆,二叉树那种有一定空间的拷贝,则会出大问题;同时,在其析构时,也面临这一块空间还在使用却被析构或者一块空间连续析构两次;
深拷贝:拷贝具有一定空间的数据时,先开辟一块空间,使拷贝的数据与被拷贝的数据不指向一个地址;
2.3.4.拷贝构造的形式:
date d2(d1);//拷贝构造;
date d2 = d1;//也是拷贝构造;
/////
2.4赋值重载函数
2.4.1赋值运算的形式:
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表):
注意:
不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型参数
用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
藏的this
.* :: sizeof ?: . 注意以上5个运算符不能重载。
///
2.4.2类比赋值重载而推广的运算符重载:
赋值重载:把一个对象赋值给另一个对象;
class date
{
public:
date(int n)//有参构造函数
{
a = 2;
}
void print()
{
cout << a << endl;
}
//d1=d2//自己实现构造函数:
//date& operator=(const date& d)//因为对象实例化过,传引用返回,减少拷贝构造;
//{ //加const防止写反;
// if (*this != d)
// {
// _year = d._year;
// _month = d._month;
// _day = d._day;
// }
// return *this;
//}
private:
int a;
};
int main()
{
date d1(1);//对象实例化
d1.print();
date d2 = d1;//赋值重载
d2.print();
}
运算符重载:通过对赋值重载的学习,我们可以模仿,自己构建更多运算符重载;
//d1==d2 或者d1.operator(d2);
bool operator==(const date& d)//传引用传参,减少拷贝,const防止写反;
{
return _year == d._year&&
_month == d._month&&
_day == d._day;
}
//d1 < d2;
bool operator<(const date& d)//传引用传参,减少拷贝,const防止写反;
{
return _year == d._year
|| (_year == d._year&& _month < d._month)
|| (_year == d._year&& _month == d._month&& _day < d._day);
}
//d1<=d2
bool operator<=(const date& d)
{
return *this < d || *this == d;
}
//d1>d2
bool operator>(const date& d)
{
return !(*this <= d);
}
//d1>=d2
bool operator>=(const date& d)
{
return !(*this < d);
}
//d1!d2
bool operator!=(const date& d)
{
return !(*this == d);
}
//d1=d2
date& operator=(const date& d)
{
if (*this != d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}