Boolan博览网C++开发课程第二周笔记

1.三大函数:拷贝构造、拷贝赋值和析构函数

1.三大函数(BigThree)
拷贝构造函数
拷贝赋值函数
析构函数
例1.

class String
{
public:
  String(const char* cstr=0); //构造函数
  String(const String& str);  //拷贝构造
  String& operator=(const String& str); //拷贝赋值
  ~String();                            //析构函数
  char* get_c_str() const{return_data;}
private:
  char* m_data
};

说明:拷贝构造和拷贝赋值中的String&可以接受自己作为参数。
析构函数的作用:当对象结束其生命周期时(例如对象所在的函数已调用完毕),系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。

2.ctor和dtor(构造函数和析构函数)

例2.

inline
String::String (const char*cstr=0)
{
 if(cstr){
   m_date=new char[strlen(cstr)+1];  //创建出足够的空间,+1的原因是字符串结尾有结束符'\0'
   strcpy(m_date,cstr);
   }
 else{
   m_date=new char[1];
   *m_date='\0';
   }
 }

inline
String::~String()
 {
  delete [] m_date; //在析构函数中用delete将动态分配的内存释放。
 }

为什么要有拷贝构造函数(copy ctor)和拷贝赋值(copy op=)
说明:使用default copy或者default op=时,即浅拷贝时,浅只是增加了一个指针指向已经存在的内存,而深拷贝就是增加一个指针并且申请一个新的内存,使这个增加的指针指向这个新的内存,采用深拷贝的情况下,释放内存的时候就不会出现在浅拷贝时重复释放同一内存的错误。因此,应该避免使用浅拷贝。

例3.深拷贝

inline
String::String (const String& str)
{
 m_date=new char[strlen(str.m_date)+1]; //分配足够的内存空间
 strcpy(m_date,str.m_date);
}

inline
String& String::operator=(const String& str)
{
 if(this==&(str) //检测是否自我赋值
  return *this;
 delete [] m_date;
 m_date=new char[strlen(str.m_date)+1];
 strcpy(m_date,str.m_date);
 return *this;
}
...
{
 String s1("hello"); 
 String s2(s1);   //直接取另一个object的private(兄弟之间互为友元)
 String s2=s1;
}

说明:
1)检测自我赋值:若使用者进行自我赋值操作,将会产生不确定结果。因为下面有delete []m_data,如果提前把m_data给释放了,指针已成野指针,再赋值就错了。
2)赋值函数中,第二步接着要释放掉m_date,因为随后m_date被赋予了新的值。

2.堆,栈与内存管理

1.栈内存和堆内存
栈(Stack)
Stack,是存在于某作用域(space)的一块内存空间,当调用函数,函数本身会形成一个Stack用来放置它所接受的参数,以及返回的地址。在函数体内声明的任何变量,其所作用的范围都取自上述Stack。
堆(Heap)
Heap,是指系统中提供的一块global内存空间,堆允许程序在运行时动态地申请某个大小的内存空间。

例4.堆内存与栈内存

class Complex{...};
...
{
 Complex c1(1,2);  //所指的空间来自栈内存。
 Complex* p=new Complex(3);  //动态分配的内存空间,堆内存。
}

2.不同object的生命周期
Stack objects 的生命周期
例4中的c1即为Stack objects,其生命在作用域结束之际结束。

静态变量的生命周期
静态变量定义时,前加static,静态变量的生命在作用域结束后仍然存在,其生命周期直到整个程序结束时才会结束。

global objects 的生命周期
global objects的生命周期在整个函数结束后才结束。

Heap objects 的生命周期

例5.

class Complex{...};
...
{
 Complex* p=new Complex;
 ...
 delete p;
}

p所指的便是Heap objects,其生命在它被delete之际才结束。

例6.

class Complex{...};
...
{
 Complex* p=new Complex;
}

说明:以上会出现内存泄漏(memory leak),因为当作用域结束时,p所指的heap objects仍然存在,指针的生命周期结束了,作用域之外再也看不到p(也没有机会再delete p)。

3.内存分配
1)new:先分配memory再调用ctor
例如:
Complex* pc=new Complex(1,2);
被编译器分成了3步:
①void* men=operator new(sizeof(Complex)); //分配内存
②pc=static_cast(mem); //转型
③pc->Complex::Complex(1,2); //构造函数

2)delete:先调用dtor,再释放memory
例如:
Complex* pc=new Complex(1,2);
….
delete pc;
被编译器转化为:
Complex::~Complex(pc); //析构函数
operator delete(pc); //释放内存

3)动态分配所得的内存块(memory block);in vc

调试模式(白色区域),new complex时的内存分配,与发布模式时的内存分配。分配的空间都是16的倍数,不足补充pad。cookie(红色区域)指的是空间大小,最低位为1表示系统给出去,0表示系统收回来。即为1表示程序得到内存空间。
这里写图片描述

4.扩展知识

1.static 静态
对于非静态函数(non-static member function)在编译器中通过不同的地址处理不同的数据。

Complex c1,c2,c3; 
cout<<c1.real(); 
cout<<c2.real();     

编译器中:

Complex c1,c2,c3;
Complex::real(&c1);
Complex::real(&c2);

三个对象的成员函数只有一份, 函数被调用时通过隐藏的this指针区分对象。
而对于静态函数(static-member function),静态函数没有this pointer,从而无法得到this。所以不能访问非静态成员。通常用来存取static成员。
调用static函数的方式有二:
1)通过object调用
2)通过class name调用

int main()
{
 Account::set_rate(5.0);  //通过class name调用 
 Account a;
 a.set_rate(7.0);  //通过object屌用
}

2.函数模板(function template)
重载函数通常用于对不同的数据类型执行相似的操作,不同数据类型的程序逻辑可能有所不同。如果每种数据类型的程序逻辑和操作都相同,则可以使用函数模板来更紧凑、更方便地实现函数重载。

{
 stone r1(2,1),r2(3,3),r3;
 r3=min(r1,r2);  //不必指出参数类型
}

编译器会对function template进行引数推导

templat<class T>
inline
const T& min(const T& a,const T& b)
{
 return b<a?b:a;
}

引数推导的结果为T为stone,于是调用Stone::operator<.

class Stone
{ 
 public:
  Stone(int w,int h,int we)
   :_w(w),_h(h),_weight(we)
   {}
  bool operator(const stone& rhs) const
   {return_weight<rhs._weight;}
 private:
  int_w,_h,_weight;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值