7.Big Three :拷贝构造、拷贝赋值、析构
. Class with pointer member
#ifndef __MYSTRING__
#define __MYSTRING__
class String
{
...
} ;
String :: function(...)...
Global-function(...)...
#endif
int main()
{
String s1() ;
String s2("hello") ;
String s3(s1) ; //拷贝构造
cout << s3 << endl ;
s3 = s2 ; //拷贝赋值
cout << s3 << endl ;
}
. 拷贝构造与拷贝赋值如果没有定义,编译器会默认一套给你
. 当class with pointer 时,要自己写拷贝构造与拷贝赋值,不能用编译器默认函数
class String
{
public :
String (const char* cstr = 0 ) ; //构造函数
String (const String& str) ; //拷贝构造函数
String& operator = (const String& str) ; //拷贝赋值操作符重载
~String () ; //析构函数
char* get_c_str() const { return m_data } ;
private :
char* m_data ; //指向字符的指针,要动态分配,不能直接放数组
}
. ctor 和 dtor 构造函数和析构函数
inline
String :: String(const char* cstr = 0 ) //构造函数,参数是有默认值的指针
{
if(cstr){ //先检查传进来的指针是否为空
m_data = new char[strlen(cstr)+1] ; //分配空间+1给结束符号\0留位置
strcpy(m_data , cstr) ;
}else{
m_data = new char[1] ; //分配1个字符空间
*m_data = '\0' ;
}
}
inline
String :: ~String() //析构函数
{
delete[] m_data ; //关门清理之前new的空间
}
. 有指针的class一定要做动态分配,用完之后要释放
. class with pointer members 必须要有copy ctor 和copy op= 拷贝构造和拷贝赋值
拷贝构造函数
. 如果使用编译器默认浅拷贝,会造成memory leak以及alias,两个指针指向同一位置,叠名
inline
String :: String(const String& str) //拷贝构造函数
{
m_data = new char[strlen(str.m_data)+1] ; //创建足够空间存放蓝本
strcpy(m_data , str.m_data) ; //深拷贝
}
copy assignment operator拷贝赋值函数
. 一定要在operator中检查是否self assignment,不然在对象删掉原来数据之后再拷贝蓝本时会产生不确定行为
inline String&
String :: operator = (const String& str)
{
if(this == &str) //self assignment检测自我赋值,防止出错
return *this ;
delete[] m_data ; //1.删掉原来数据
m_data = new char[strlen(str.m_data)+1] ; //2.创建和蓝本一样大的空间
strcpy(m_data , str.m_data) ; //3.拷贝蓝本
return *this ;
}
8.堆、栈 与内存管理
output函数
. output operator操作符重载
. 只能写成全局函数,如果写为成员函数会改变操作符使用方向把cout放到右边去
#include <iostream>
ostream& operator << (ostream& os , const String& str)
{
os << str.get_c_str() ;
return os ;
}
{
String s1(“hello ”) ;
return os ;
}
stack栈,heap堆
. stack是存在于某作用域scope的一块内存空间memory space。调用函数时,会形成一个stack来存放所接收的参数和返回的地址
. 在函数本体function body内声明的任何变量所使用的内存块都取自stack
. Heap,又称system heap 是操作系统提供的global全局内存空间,程序动态分配dynamic allocated 从中获得若干区块blocks,用new来动态取得
. 离开scope后Stack中创建的数据生命结束,在Heap中new的数据离开作用域后依然存在需要手动delete掉
. stack objects的生命期,在scope结束之后动调用析构函数,又称auto object,被自动清理
. static local object 生命期 ,在scope结束后依然存在,直到整个程序结束,析构函数在程序结束调用
. global object 的生命期,在scope外定义,生命直至main结束
. heap object的生命期,new之后要delete,防止内存泄漏。
new和delete
. new:先分配memory,再调用ctor构造函数
. new编译时被分解为三个动作:分配内存、转换存储类型、通过指针调用构造函数
. delete:先调用dtor析构函数,再释放memory
. delete编译时分解为两个动作:调用析构函数、释放内存
. 如果类中没有定义析构函数,没有指针时编译器会在local结束自动清理内存,有指针时必须定义析构函数释放动态分配的内存,不然会产生memory leak
动态分配所得的内存块memory block,(in VC)
. 例如new complex,会获得2个double数据位置,调试时候会在数据前后多获得一些内存和cookies,分配内存要为16的倍数,不足16会用pad补齐。不在调试模式下不需要debugger header,于是刚好为16个byte。上下cookies用来记录整块大小,一个cookie占用4byte。malloc和free函数以cookies为前后标记,分配时cookies后一位是1,由于大小为16整数倍,所以cookies最后四位是0用来记录分配还是收回。
动态分配所得array
. 在良好的变成习惯中,new array[]一定要搭配delete[]
Complex* p = new Complex[3] ;
...
delete[] p ; //如果new的是array,delete一定要加[]
//加[]会调用三次dtor析构函数释放所以指针
//如果不加[],只调用一次dtor,则会造成memory leak
9.String类的实现过程总结
class String
{
public :
String(const char* cstr = 0) ; //接受一个指针为初值,默认值为0
String(const String& str) ; //拷贝构造
String& operator = (const String& str) ; //返回如果不是local object,则返回reference
~String() ; //析构函数
char* get_c_str() const { return m_data} ; //返回字符串的辅助函数
private :
char* m_data ; //数据为动态分配数组
}
inline //尽量让函数成为inline
String :: String(const char* cstr = 0) //构造函数
{
if(cstr){
m_data = new char[strlen(cstr)+1] ; //调用其他函数记得include该头文件
strcpy(m_data , cstr) ;
}else{
m_data = new char[1] ;
*m_data = '\0' ;
}
}
inline
String :: ~String() //析构函数一定要写
{
delete[] m_data ; //前面有用array new,这里也要array delete
}
inline
String :: String(const String& str)
{
m_data = new char[strlen(str.m_data)+1] ; //分配足够大的空间
strcpy(m_data , str.m_data) ; //把初值拷贝进来
}
inline //复杂函数也写成inline是没有关系的
String& String :: operator = (const String& str) //&符号放在typename后表示引用
{
if(this == &str) //判断自我赋值,&符号放在变量前为取地址
return *this ;
delete[] m_data ;
m_data = new char[strlen(str.m_data)+1] ;
strcpy(m_data , str.m_data) ;
return *this ; // 返回值以支持连续赋值
}
10.类模版、函数模板、及其他
补充一:static
. 静态:static加在数据或者函数前面、
. 调用相同函数时使用了不同的地址,this指的调用函数的object的地址
. 一个函数要被很多对象调用时,由this pointer来告诉函数调用哪个地址.
. 数据加上static之后,数据与对象脱离,在内存某区域单独纯在处理,只有一份
. 函数加上static之后,成为静态函数,没有this pointer,不能处理对象,用来存取静态数据
. 静态数据一定要在函数外给出初值
class Account
{
public :
static double m_rate ; //静态数据
static void set_rate(const double& x){m_rate = x ;} //静态函数
}
double Account :: m_rate = 8.0 ; //静态数据一定要在class外给出定义
//定义可使变量获得内存,可同时给出初值
int main()
{ //调用静态函数方式有两种
Account :: set_rate(5.0) ; //一种,通过object调用
Account a ; //二种,通过class name调用
a.set_rate(7.0) ;
}
补充二:把ctors放在private区
. 只希望产生一个对象的class,如singleton
class A
{
public :
static A& getInstance {return a ;} ;
setup() {...}
private :
A() ;
A(const A& rhs) ;
static A a ; //一个A本身a已经存在于static,外界创建不了A
...
}
class A{
public :
static A& getInstance() ;
setup() {...}
private :
A() ;
A(const A& rhs);
} ;
A& A :: getInstance()
{
static A a ; //也可将静态A创建写进函数中,防止空间浪费
return a; //有人使用函数时才会被创建,离开继续存在
}
补充三:cout
. ostream中将cout的<<操作符进行各种类型重载,cout即可接受各种不同类型数据
补充四:class template ,类模版
template <typename T> //告诉编译器模板名称T
class complex
{
public :
complex(T r=0 ; T i=0) //数据类型写为T
: re(r) , im(i)
{}
...
T real() const {return re ;}
T imag() const {return im ;}
private :
T re , im ;
...
}
{
complex<double> c1(2.5 , 1.5) ; //模板使用方法
complex<int> c2(2 , 6) ; //T会被替换为<>中的类型
}
补充五:function template , 函数模板
. 当
template <class T>
inline const T&
min(const T& a , const T& b)
{
return b<a?b:a ; //引数推导结果调用类中<操作符重载函数
}
. 编译器会对function template进行引数(实参)推导argument deduction
. c++中算法都为function template 形式
补充六: namespace
namespace std //std会被全部包在一起使用
{
... //其中内容会被包在一起
}
. using directive
#include <iostream>
using namespace std ; //将全部std包进去
int main()
{
cin << ...;
cout <<...;
return 0 ;
}
. using declarating
#include <iostream>
using std::cout ; //仅包入cout
int main()
{
std::cin <<...;
cout <<...;
return 0 ;
}
. 不使用namespace
include <iostream>
int main()
{
std::cin <<...;
std::cout <<...;
return 0;
}
补充七:其他
. operator type 转换函数
. explicit
. Namespace
. template specialization
. Standard Library
. auto
...
.