一、三大函数
拷贝构造,拷贝赋值,析构函数
1.class的两个经典分类
1)class without point member
2)class with point member
必须自定义拷贝构造,拷贝赋值
class String
{ };
String s3(s1); //拷贝构造---因为在构造一个对象s3
s3 = s2; //拷贝赋值
如果程序中没写拷贝构造和拷贝赋值,编译器会自动生成;如果类里面带指针,不能使用编译器自带的,要自己写。
拷贝构造函数,
inline String::String(const String& srt)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
拷贝赋值,
要先将左侧内容清空,再进行赋值
inline String& String::operator = (const String& str)
{
if (this == &str) //检测自我赋值
return *this;
delete[ ] m_data; //杀掉自己
m_data = new char[ strlen(str.m_data) + 1]; //重新创建一个足够大的空间,+1是 \0 结束符
strcpy(m_data, str.m_data); //深拷贝,完全一样
return *this;
}
class String
{
public:
String(const char* cstr = 0); //构造函数
String(const String& str); //拷贝构造,String接收的是String&他自己这种东西
String& operator = (const String& str);
~String(); //析构函数,当对象死亡(离开作用域)调用
private:
char* m_data; //数据成员要定义一个指向字符的指针
};
class中有指针,要做动态分配
m_data = new char[1];
2.字符串---指针指着第一个位置,一串,最后结束符 \0 结束。
a + rand() % (b-a+1) 就表示 a~b之间的一个随机数
二、堆,栈与内存管理
1. stack(栈),heap(堆)
stack是存在于某作用域的一块内存空间。eg当你调用函数,函数本身即会形成一个stack用来放置它所接收的参数,以及返回地址。
heap,由操作系统提供的一块global内存空间,程序可动态分配从某中获得若干区块。
class Complex{... };
...
{
Complex c1(1,2); //离开作用域其生命结束,他的析构函数会自动被调用
Complex* p = new Complex(3); //从堆中获得内存,结束时需要delete释放
}
大括号即是一个作用域。c1所占用的空间来自stack
2. static local objects的生命期
class Complex {...};
...
{
static Complex c2(1,2);
} // c2是静态对象(static object),其生命在作用域结束之后仍然存在,直到整个程序结束。
3. global objects 的生命期(写在任何作用域之外的)
class Complex {...};
...
Complex c3(1,2);
int main()
{...}
c3是全局对象,生命期在整个程序结束之后才结束。也可视为一种static object,作用域是『整个程序』。
4. heap objects的生命期
使用new的标准写法:
class Complex {...};
...
{
Complex* p = new Complex;
...
delete p;
}
p指的是heap object,使用new定义了一个对象,就要用delete调用其析构函数进行释放。
如果没有delete会导致内存泄漏。程序结束之后,指针死掉,但是指针所指的内存仍然存在,这就是内存泄漏。
5.
5-1 new:先分配memory,再调用构造函数
Complex* pc = new Complex(1,2);
new编译器转化为(1)(2)(3)三个动作
Complex* pc;
(1)void* mem = operator new(sizeof(Complex)); //分配内存
函数名字 operator new,内部调用malloc(n)
(2)pc = static_cast
(3)pc->Complex::Complex(1,2); //指针pc调用构造函数,即(1)图中内存起始值
5-2 delete:先调用析构函数,在释放内存
String* ps = new String(“Hello”);
...
delete ps;
编译器转化为:
(1)String::~String(ps); //首先调用析构函数,将字符串里面动态分配部分杀掉
(2)operator delete(ps); //其内部调用free(ps) ,释放内存
6. 动态分配所得的内存块(memory block),in VC编译器中
只有使用new的情况下,如下图分配
动态分配所得的array
array new要搭配 array delete,不搭配会出错
m_data = new char[strlen(cstr)+1];
delete[ ] m_data;
例如,
右侧如果delete不写[ ],只调用一次析构函数,另外两块内存没有被杀掉。
三、复习String类的实现过程
1. 定义一个字符串String类 在头文件(.h)中实现
1) 写出类名
class String
{ };
2)字符串的设计一般做法:里面放个指针(将来要放多大的字符串的内容,用new的方式动态的去分配一块内存)
class String
{
public:
private:
char* m_data;
};
3)思考要用到哪些函数 放到public
首先是构造函数(跟class同名,无返回值,也不可是void类型);class里面带指针,要想到三大函数(拷贝构造--拷贝赋值--析构函数)
三大函数要改变数据成员,所以函数不是const类型。形参加const,不改变传入值!
class String
{
public:
String(const char* cstr = 0);
String(const String& str); //拷贝构造函数,&按引用传递参数,不改变传入的值--加const
String& operator=(const String& str); //拷贝赋值函数
~String( ); //析构函数
char* get_c_str() const { return m_data; } // 【1】
private:
char* m_data;
};
【1】若将成员函数声明为const,则该函数不允许修改类的数据成员
以上代码完成接口设计///
2. ctor和dtor(构造函数和析构函数)在类外定义
1)ctor
inline
String::String(const String* cstr = 0)
{
//分配足够的空间来放初值,并判断传进来的指针是否有东西
if(cstr)
{
m_data = new char[strlen(cstr)+1]; //字符串最后有一个结束符 '\0' ,所以大小+1
strcpy(m_data, cstr); //将传进来初值内容拷贝给新分配的空间
}
else
{ //未指定初值
m_data=new char[1];
*m_data = '\0';
}
}
在c++中使用strlen,strcpy函数要引用#include
定义的函数尽量用inline
# new的一般使用格式
<1> 指针变量名=new 类型标识符;
<2>指针变量名=new 类型标识符(初始值);
<3>指针变量名 =new 类型标识符[内存单元个数];
######################################
2)dtor //此处析构函数动作比较少,加上inline
inline
String::~String( )
{
delete[ ] m_data; //因为上面是array,所以在此处array delete
}
3. copy ctor (拷贝构造函数)
inline
String::String(const String& str)
{
m_date = new char[strlen(str.m_data)+1];
strpy(m_data, str.m_data);
}
4. copy assignment operator(拷贝赋值函数)
先写出函数名称; 从来源端到目的端(目的端是原来已经存在的,要先delete)
inline
String& String::operator= (const String& str) //此处&是引用
{//首先判断是否是自我赋值(通过看来源端和目的端是否相等)
if(this == &str) //this是指针,此处&是取地址
return *this;
delete[ ] m_data;
m_data = new char[ strlen(str.m_data)+1 ];
strcpy(m_data, str.m_data);
return *this; //*取值
}
四、类模板,函数模板及其他
1.static (静态)
在数据或者函数前面加static 就变成静态类型
附加:作业涉及到的内容
类的继承与派生
被继承的已有的类成为基类
派生出的新类成为派生类
直接参与派生出某类的基类称为直接基类
基类的基类甚至更高层的基类成为间接基类
+++单继承时(派生类只有一个直接基类)
class 派生类名:继承方式 基类名
{ 成员声明;}
eg:
class Derived:public Base
{
public:
Derived();
~Derived();
};
+++多继承时
class 派生类名:继承方式1 基类名1,继承方式2 基类名2,...
{ 成员声明;} //每一个继承方式,只用于限制对紧随其后之基类的继承
eg:
class Derived: public Base1, private Base2
{
public:
Derive();
~Derive();
};
派生类的构造和析构函数
基类的构造函数不被继承,派生类需要定义自己的构造函数,去进行初始化
定义一个派生类对象,从基类继承过来的成员,要调用基类的构造函数去初始化(编译器自动执行)。首先要初始化基类的成员,再执行派生类的构造函数和函数体。
派生类要给基类的构造函数调用提供参数。