C++之构造函数-拷贝构造
一,类和对象
1,对象
万物皆对象,任何一种事物都可以看作是对象。
2,面向对象
2.1 如何描述对象
通过对象的属性(名词、形容词、数量词)和行为(动词)表达对象。
冰箱属性:品牌、容量、颜色、功耗
冰箱行为:装东西、冷冻、冷藏..
2.2 面向对象的程序设计
对自然界的观察引入到编程实践中的一种理念和方法
这种方法得益于“数据抽象”,在描述对象时把细节的东西剥离出去,只考虑一般性,有规律性的,统一的东西
3 类
类是将多个对象共性提取出来 定义的一种新的数据类型,是对 对象的属性和行为的抽象描述,对象是类的实例化。
属性 行为
狗 犬种 进食
犬龄 睡眠
体重 玩耍
毛色
二, 类的定义与实例化
1 类的一般形式
class/struct 类名:继承方式 基类
{
访问控制限定符:
类名(形参表):初始化表
{ //构造函数
//函数体
};
~类名(void)
{
//析构函数
//函数体
}
返回类型 函数名(形参表)
{//成员函数
//函数体
}
数据类型 变量名;//成员变量
};
学生:
属性 行为
名字 吃饭
年龄 睡眠
学号 学习
2 访问控制限定符
public:公有成员,谁都可以访问该成员
private:私有成员,只有自己可以访问该成员
protected:保护成员。//后面讲
在C++类中class和struct没有本质的区别,唯一不同在于class的缺省访控属性为私有,而struct的访控属性为公有
三 ,类的定义与实例化
1 类的一般形式
class/struct 类名:继承方式 基类
{
访问控制限定符:
类名(形参表):初始化表//构造函数
{
//函数体
};
~类名(void)//析构函数
{
//函数体
}
返回类型 函数名(形参表)//成员函数
{
//函数体
}
数据类型 变量名;//成员变量
};
2 访问控制限定符
public:公有成员,谁都可以访问该成员
private:私有成员,只有自己可以访问该成员
protected:保护成员。//后面讲
在C++类中class和struct没有本质的区别,唯一不同在于class的缺省访控属性为私有,而struct的访控属性为公有
3 构造函数(Constructors)
类名(构造形参表)[:初始化表]
{
//构造函数体;
}
1)构造函数名与类名相同,且没有返回类型
2)构造函数在对象创建时自动被调用
3)构造函数负责确定对象初始化状态以及分配必要的内存
4)构造函数在每个对象的整个声明周期内,一定会被调用,且仅会被调用一次。
4 对象的创建和销毁
1)在栈中创建单个对象
类名 对象(构造实参表);
类名 对象 = 类名(构造实参表);
2)在栈中创建多个对象
类名 对象数组[元素个数] = {类名(构造实参表),...}
3) 在堆中创建/销毁单个对象
创建:
类型* 对象指针 = new 类名(构造实参表);
销毁:
delete 对象指针
4)在队中创建/销毁多个对象
创建:
//这种初始化的写法,只在c++2011支持
类名* 对象指针 = new 类名[元素个数]{类名(参数),...}
销毁:
delete[] 对象指针
5 类的声明和定义可以分别放在不用的文件中
类声明部分放在xx.h
类实现部分放在xx.cpp
使用该类的代码通过在其它的XX.cpp中
例子:
#include <iostream>
#include <cstdio>
using namespace std;
class Clock
{
public:
Clock(bool timer = false):m_hour(0),m_min(0),m_sec(0)
{
if(timer)
{
time_t t = time(NULL);
tm* local = localtime(&t);
m_hour = local->tm_hour;
m_min = local->tm_min;
m_sec = local->tm_sec;
}
}
void run(void)
{
for(;;)
{
//显示时间
show();
//计时
tick();
}
}
private:
void show(void)
{
printf("\r%02d:%02d:%02d",m_hour,
m_min,m_sec);
fflush(stdout);
}
void tick(void)
{
sleep(1);
if(++m_sec == 60)
{
m_sec = 0;
if(++m_min == 60)
{
m_min = 0;
if(++m_hour == 24)
m_hour = 0;
}
}
}
int m_hour;
int m_sec;
int m_min;
};
int main(void)
{
//Clock clock(true);//时钟
Clock clock;//计时器
clock.run();
return 0;
}
四, 构造函数和初始化表
1 构造函数重载
构造函数通过参数表的差别化形成重载,创建对象通过构造实参选择匹配,表示不同对象的创建方式。
2 缺省构造函数
如果一个类没有定义构造函数,编译器会提供一个缺省构造函数(无参构造函数),使成员变量获得定义。
1)对于基本类型的成员变量不初始化
2)对类 类型的成员 变量,调用相应类型的无参构造函数。
3)如果定义了构造函数,无论是否有参数,编译器都不会再提供无参构造函数。
3 类型转换构造函数(单参构造函数)
class 目标类型
{
目标类名(源类型 src){...}
};
使用explicit关键字修饰类型转换构造函数,可以强制这种转换必须显示地进行。
4 拷贝构造函数
类名 (const 类名& that){...}
1)用一个已经存在的对象构造同类型的副本对象时,会调用该类的拷贝构造函数
class A{..};
A a1;//无参方式构造
A a2=a1;//拷贝方式构造
2)如果一个类没有定义拷贝构造函数,那么编译器会为其提供一个缺省拷贝构造函数
a,对基本类型的成员变量,按字节复制
b,对类类型的成员变量(成员子对象),调用相应类型的拷贝构造函数。
3)如果自己定义拷贝构造函数,编译器将不再提供缺省拷贝构造函数,这时要实现成员复制有关操作,必须在自己
定义的拷贝构造函数中编码完成。
4)拷贝构造函数调用时机
a.用已经定义对象作为同类型对象的构造实参
b.以对象的形式向函数传递参数。
c.从函数中返回对象(有时会被编译器优化掉)
5 构造函数初始化表
1)指明类中成员变量的初始化方式
类名(形参表):成员变量1(参数1),成员变量2(常量)..
{
构造函数体;
}
2)必须要使用初始化表的地方
a.如果有类 类型的成员变量,而该类又没有无参构造函数,则必须通过初始化初始化该成员变量。
b.类中包含"const"和引用"&"成员变量,必须在初始化表中显示的初始化。
c.成员变量初始化顺序由声明顺序决定,而与初始化表的顺序无关。
五 ,this指针与常函数
1.this指针
1)类的构造函数和成员函数中都隐藏一个该类类型的指针参数,名为this。
a.对于普通的成员函数,this指针就是指向调用该函数的对象。
b.对于构造函数,this指针就是指向这个正在被构造的对象。
2)this指针可以显示的使用,需要显示使用this指针的地方:
a.区分作用域
b.从成员函数中返回对象自身(自引用)
c.从类的内部销毁对象自身(自销毁)
d.作为函数的实参,从一个对象传递给另一个对象,实现对象间的交互。
2 常函数
1)在一个成员函数参数表后面加const,这个成员函数就称为常函数。
返回类型 函数名(形参表) const{函数体}
2)常函数中this指针是一个常指针,不能在常函数中修改该成员变量的值。
3)被mutable关键字修饰的成员变量可以在常函数中被修改
4)非常对象可以调用常函数,可以调用非常函数,但是常对象只能调用常函数,不能调用非常函数。
5)函数名和形参表相同的成员函数,其常版本和非常版本可以构成重载关系,常对象调用常版本,非常对象调用非常版本
六 ,析构函数(Destructor)
1 析构是类的特殊的成员函数
class 类名
{
~类名(void){...}//析构函数
};
1)函数名必须“~类名”
2)没有返回类型,也没有参数
3)不能被重载,一个类只能有一个析构函数
4)主要负责清理对象生命周期内动态产生的资源
2.当对象被销毁时,该对象的析构函数自动被执行
1)栈对象离开作用域时,其析构函数被作用域终止右花括号"}"调用;
2)堆对象的析构函数被delete运算符调用。
3 如果一个类没有显示定义析构函数,那么系统会为该类提供一个缺省析构函数:
1)对基类类型的成员变量,什么也不做;
2)对类 类型的成员变量,调用相应类的析构函数,析构成员子对象。
4 对象的创建和销毁的过程
1)对象的创建
-->为对象分配内存
-->按声明顺序依次调用成员子对象的构造函数
-->执行构造函数体的代码
2)对象销毁
-->执行析构函数的代码
-->按声明逆序依次调用成员子对象的析构函数
-->释放对象所占的内存空间
七 , 拷贝构造和拷贝赋值
1 浅拷贝和深拷贝
如果一个类包含指针形式的成员变量,缺省拷贝构造函数只是赋值指针成员变量本身,而没有复制该指针所指向的内容,这种拷贝构造被称为浅拷贝。
浅拷贝将导致不同对象间的数据共享,同时会在析构函数中引发"double free"异常,为此必须自己定义一个支持深拷贝的拷贝构造函数。
2 类的缺省拷贝赋值函数和缺省拷贝构造类似,是浅拷贝,为了得到深拷贝的赋值效果,就必须自己定义一个支持深拷贝赋值的运算符函数。
1)防止自赋值
2)释放旧资源
3)分配新资源
4)拷贝新数据
5)返回自引用
#include <iostream>
using namespace std;
class Integer
{
public:
Integer(int data = 0):m_data(new int(data)){}
/*缺省拷贝构造,浅拷贝
Integer(const Integer& that):m_data(that.m_data){}*/
/*自定义深拷贝*/
Integer(const Integer& that):m_data(new int(*that.m_data)){}
/*缺省拷贝赋值函数*/
/*Integer& operator=(const Integer& that){
m_data = that.m_data;
return *this;
}*/
/*深拷贝赋值函数*/
Integer& operator=(const Integer& that)
{
if(&that != this)
{//防止自赋值
delete m_data;//释放旧资源
//分配新资源,拷贝新数据
m_data = new int(*that.m_data);
}
return *this;//返回自引用
}
~Integer(void){
cout << "Integer::~Integer()" << endl;
delete m_data;
m_data = NULL;
}
int get(void){
return *m_data;
}
private:
int* m_data;
};
int main(void)
{
Integer i(100);
cout << i.get() << endl;
Integer i2(i);//拷贝构造
cout << i2.get() << endl;
Integer i3;
i3 = i2;//拷贝赋值,i3.operator=(i2).
cout << i3.get() << endl;
return 0;
}