C++的6种默认函数及运算符重载

C++类和对象

我们知道C语言是面向过程的编程语言,而C++是面向对象(OPP)的编程语言。 
面向对象是一种程序设计范型,也是一种程序开发的方法。而对象指的是类的实例,将对象作为程序的基本单元,将程序和数据封装在里面,以提高软件的重要性、灵活性和扩展性。 
类(class)是C++中的特有的,形似于C语言中的结构体! 
类有三大特性:封装、继承、多态 
在一个类中,可以分为两种对象。分别是成员变量(数据)和成员函数(函数)。

封装 (encapsulation) 封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合。 
封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过 外部接口,一特定的访问权限来使用类的成员

多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,>>>父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作<<<(摘自“Delphi4 编程技术内幕”)。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。

“继承”是面向对象软件技术当中的一个概念。如果一个类A继承自另一个类B,就把这个A称为”B的子类”,而把B称为”A的父类”。继承可以使得子类具有父类的各种属性和方法,而不需要再次编写相同的代码。

在类中有三种访问限定符分别是:1、public(公有)2、protectd(保护)3、private(私有)

1 . public成员可从类外部直接访问,private/protect ed成员不能从类外部直接访问。 
2 . 每个限定符在类体中可使用多次,它的作用域是从该限定符出现开始到下一个限定符之前或类体结束前。 
3 . 类体中如果没有定义限定符,则默认为私有的。 
4 . 类的访问限定符体现了面向对象的封装性。

类的作用域:

1 . 每个类都定义了自己的作用域,类的成员(成员函数/成员变量)都在类的这个作用域内,成员函数内可任意访问成员变量和其它成员函数。 
2 . 对象可以通过 . 直接访问公有成员,指向对象的指针通过 -> 也可以直接访问对象的公有成员。 
3 . 在类体外定义成员,需要使用 : : 作用域解析符指明成员属于哪个类域。

在类中可以写一个或多个函数,这种函数的作用域可以使类域,也可以是全局域。既可以在流泪中写函数,也可以在类外写,只不过要在类中声明,并且加作用域解析符。

class AA//AA为类名
{
pubilc://公有访问限定符
void Display()//Display就是成员函数
{
cout<<"Display()"<<endl;
}
private://私有访问限定符
int year;//这些为成员变量
int month;
int day;
};

这种函数是在类里面定义的成员函数,也可以在类外定义

class AA
{
public:
void Display();
private:
int year;
int month;
int day;
};

void AA::Display() //表明Display函数属于AA类
{
cout<<"Dispaly()"<<endl;
}
类实例化对象

1 . 类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。 
2 . 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间存储类成员变量。 
3 . 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑 
存在,同样的类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。

int main()
{
AA d1;
d1.Display();
return 0;
}

这里的d1就是AA实例化出来的对象。 
在结构体中计算大小是所有成员的大小之和。而类的大小也是成员变量的大小之和。

结构体内存对其规则:

1 . 第一个成员在与结构体变量偏移量为0 的地址处。 
2 . 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 
//对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 
V S中默认的值为8 
gc c中的默认值为4 
3 . 结构体总大小为最大对齐数(每个成员变量除了第一个成员都有一个对齐数)的整数倍。 
4 . 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体 
的对齐数)的整数倍。

class BB
{
char ch;//0 0-7
double d;//8 8 8 8-15
};

这个简单的类,他的大小就是char 和 double的大小,但要满足对齐规则。所以大小就应该是16。

class BB
{
char ch;//0 0-7
double d;//8 8 8 8-15
};
class CC
{
public:
char a;//0 8 0
BB b; // 16 8 8-23
char c;//24 25-31
};

这种涉及到嵌套的类求大小也满足对齐规则。

Test()
{
cout<<sizeof(CC)<<endl;
}


在类中有隐含的this指针

1 . 每个成员函数都有一个指针形参,它的名字是固定的,称为t his 指针,t his 指针是隐式的。(构造函数比较特殊,没有这个隐含t his 形参) 
2 . 编译器会对成员函数进行处理,在对象调用成员函数时,对象地址作实参传递给成员函数的第一个形参t his 指针。 
3 . t his 指针是成员函数隐含指针形参,是编译器自己处理的,我们不能在成员函数的形参中添加t his 指针的参数定义,也不能在调用时显示传递对象的地址给t his 指针。

void Display()
{
cout<<year<<"-"<<month<<"-"<<day<<endl;
}
//上面类中的函数就相当于
void Display(AA *this)
{
cout<<*this->year<<"-"<<*this->month<<"-"<<*this->day<<endl;
}

this指针并不是用户在编写代码时所要写的,而是编译器在处理成员函数时所要做的工作。

类的六个默认成员函数

1、构造函数 
2、拷贝构造函数 
3、析构函数 
4、赋值操作符的重载 
5、取地址操作符的重载 
6、const修饰的取地址操作符的重载

前四个一般在实际中应用最多,所以只看前四个 
先来看看构造函数

构造函数

在类中,私有的成员变量在类外是访问不到的那么要怎么对他进行初始化呢?这时候就要有一个共有函数来对他进项初始化,同时这个函数应该有且仅在定义对象时自动执行一次,这时调用的函数称为构造函数。

构造函数有以下特征

1 . 函数名与类名相同。 
2 . 无返回值。 
3 . 对象构造(对象实例化)时系统自动调用对应的构造函数。 
4 . 构造函数可以重载。 
5 . 构造函数可以在类中定义,也可以在类外定义。 
6 . 如果类定义中没有给出构造函数,则C + + 编译器自动产生一个缺省的构造函数,但只要我们定义了一个构造函数,系统就不会自动 
生成缺省的构造函数。 
7 . 无参的构造函数和全缺省值的构造函数都认为是缺省构造函数,并且缺省的构造函数只能有一个。

class AA
{
public:
void Display();
private:
int year;
int month;
int day;
};

void AA::Display()
{
cout<<year<<month<<day<<endl;
}

这个类在调用Display函数时,会将类里面的私有成员变量的值打印输出。 
这里会输出什么值呢?


输出的是随机值,这其实就是调用了一次构造函数,只不过这个构造函数是系统自动生成的,没有对成员变量进行初始化。可以用户自己定义一个构造函数

class AA
{
public:
void Display();
AA(int _year, int _month, int _day)
{
year = _year;
month = _month;
day = _day;
}
/*
AA(int _year = 1900, int _month = 1, int _day = 1)
{
year = _year;
month = _month;
day = _day;
}
*/
private:
int year;
int month;
int day;
};
Test()
{
AA a1;
AA a2(2017,7,1);
}

这种是由用户自定义的构造函数,可以是无参的构造,也可以是带参数的构造函数,也可以是有缺省和半缺省的构造函数,具体实现要看具体过程。

拷贝构造函数

创建对象时使用同类对象来进行初始化,这时所用的构造函数称为拷贝构造(CopyConstructor ),拷贝构造函数是特殊的构造函数。 
拷贝构造函数有以下特征:

1 . 拷贝构造函数其实是一个构造函数的重载。 
2 . 拷贝构造函数的参数必须使用引用传参,使用传值方式会引发无穷递归调用。(思考为什么?) 
3 . 若未显示定义,系统会默认缺省的拷贝构造函数。缺省的拷贝构造函数会,依次拷贝类成员进行初始化。

AA (const AA& d)
{
year = d.year;//year是私有变量成员,可以在类中用拷贝构造将私有成员拷贝到公有成员函数里,可以达到访问私有变量的目的
month = d.month;
day = d.day;
}

在调用拷贝构造函数时

test()
{
AA a1;
AA a2(a1);
AA a3 = a1; //这也可以表示成拷贝构造
}
析构函数

当一个对象的生命周期结束时,C + + 编译系统会自动调用一个成员函数,这个特殊的成员函数即析构函数(destructor ) 
构造函数是特殊的成员函数,其特征如下:

1 . 析构函数在类名加上字符~ 。 
2 . 析构函数无参数无返回值。 
3 . 一个类有且只有一个析构函数。若未显示定义,系统会自动生成缺省的析构函数。 
4 . 对象生命周期结束时,C + + 编译系统系统自动调用析构函数。 
5 . 注意析构函数体内并不是删除对象,而是做一些清理工作。(怎么理解这里的清理工作?参看下面的EXP0 )

~AA()//析构函数在程序结束时会自动调用
{
cout<<"~AA()"<<endl;
}

系统调用析构函数是判断生成的构造函数和拷贝构造的次数来决定调用几次析构函数。

运算符重载

为了增强程序的可读性,C + + 支持运算符重载。 
运算符重载特征:

1 . operator + 合法的运算符 构成函数名(重载< 运算符的函数名:operator < )。 
2 . 重载运算符以后,不能改变运算符的优先级/结合性/操作数个数。

AA& operator=(const AA& d)//将=运算符进行重载,这里用引用传参和引用返回能够更加快速的调用,同时减少不用再压栈
{
if(this != &d)
{
year = d.year;
month = d.month;
day = d.day;
}
return *this;
}
Test()
{
AA d1(1900,1,1);
AA d2;
d2 = d1;
}

有以下几个运算符不支持重载 
. * /: : /s izeof/?: /.

C++中隐含的this指针

先来看看什么this指针

1 . 每个成员函数都有一个指针形参,它的名字是固定的,称为t his 指针,t his 指针是隐式的。(构造函数比较特殊,没有这个隐含t his 形 
参) 
2 . 编译器会对成员函数进行处理,在对象调用成员函数时,对象地址作实参传递给成员函数的第一个形参t his 指针。 
3 . t his 指针是成员函数隐含指针形参,是编译器自己处理的,我们不能在成员函数的形参中添加t his 指针的参数定义,也不能在调用时 
显示传递对象的地址给t his 指针

在刚刚上面的运算符重载问题里我们只是传了一个const AA& d,而实际上是什么样的呢?编译器在处理的时候是怎么将这个赋值运算符重载进行编译的。

    AA& operator=(AA *thisconst AA& d)
{
if(this != &d)
{
year = d.year;
month = d.month;
day = d.day;
}
return *this;
}


重载函数在接收到的第一个参数相当于是接收到d1的地址,然后通过this指针来隐藏,而编译器在处理的时候会把this展开成d1。所以在cpp类中一般都会有默认的this指针来接收。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值