C++ Primer Plus 第12章

.h
class StringBad
{
     static int num_strings;
}
.cpp
int StringBad::num_strings = 0;
不能在类声明中初始化静态成员变量 (普通变量类内类外则都不行)

//浅拷贝的坑
class StringBad
{
     StringBad()
     {
          str = new char[4];
          std::strcpy(str, "C++");
     }
     char *str;
}
void call(StringBad sb)     //按值传递
{
     //..
}
那么当
StringBad str;
call(str);    
//1 调用默认复制构造函数,将str对象的每个成员都复制函数的形参:一个新的StringBad对象sb中,于是对象char *str,只拷贝了*str的指向的地址,即浅拷贝
//2 call函数结束后,局部自动变量sb将销毁,并自动调用~StringBad的析构函数,将*str指向的地址销毁
//3 由于外部对象str与函数局部对象sb的char *str指向同一片内存,所以在之后的调用中str中的char *str将会出错s

//默认复制构造函数
StringBad sailor = sports;
这里新生成了一个StringBad对象,其调用了什么样的构造函数?答案是:不是默认构造函数,而是复制构造函数,其在用一个对象初始化另一个对象时使用,即
StringBad sailor = StringBand(sports); //StringBad(const StringBad &)

//隐式成员函数 C++自动提供如下成员函数
1 默认构造函数,如果没有定义构造函数
2 复制构造函数,如果没有定义
3 赋值操作符,如果没有定义
4 默认析构函数,如果没有定义
5 地址操作符,如果没有定义                          //返回调用对象的地址(this指针的值)

//默认构造函数
如果没有定义任何一个构造函数,C++提供一个没有任何参数和操作的构造函数

//复制构造函数(巨坑)
类的复制构造函数原型为:Class_name(const Class_name &)
(1)何时调用复制构造函数
     StringBad ditto(motto);                                   //call StringBand(const StringBad &)
     StringBad metoo = motto;                              //call StringBand(const StringBad &)     //这两种方法可能直接使用复制构造函数创建对象(vs2010方法)
     StringBad also = StringBad(motto);                    //call StringBand(const StringBad &)     //也可能使用复制构造函数生成临时变量,再进行赋值
     StringBad *pStringBad = new StringBad(motto)     //call StringBand(const StringBad &)
    
     即复制构造函数也是一种构造函数,只有当新生成对象时会调用(若 String b,a;b=a; 则会调用赋值操作符函数)

(2)复制构造函数的功能
   默认的复制构造函数逐个复制非静态成员函数(即浅拷贝,ex:指针只会复制指针本身的值,而不会复制指针指向的内存),复制的是成员的值
  
最好自己实现复制构造函数,以实现计数或者深拷贝

//赋值操作符
赋值操作符的原型为:Class_name & Class_name::operator=(const Class_name &)
->C++自己提供的默认赋值操作符函数会逐个调用其成员的operator=方法
(1)何时使用赋值操作符
将已有的对象赋给另一个对象时,将使用重载的赋值操作符(即对象用=号)
不过,初始化对象时,不一定会使用赋值操作符
StringBad metoo = knot;     //使用复制构造函数,只是有可能使用赋值构造函数(vs不会)

最好自己实现赋值构造函数(实现深拷贝等),不过,应该注意以下问题
1 由于自己可能引用了以前分配的数据,所以函数应使用delete[] 来释放这些数据
2 函数应该避免将对象赋给自身;赋值,给对象重新赋值之前,释放内存操作可能删除对象的内存(即第一步释放后,又进行拷贝)
3 函数应返回一个指向调用对象的引用 --> 主要是为了实现连等赋值(s0 = s1 = s2 --->  s0.operator=(s1.operator=(s2)); 直接 return *this 即可)

//与delete[] 兼容
str = new char[1];     //兼容
str = new char;          //不兼容
str = 0;               //兼容,空指针兼容
char words[15] = "bad idea";          //结果未知,不要这么做
char *p1 = words                         //结果未知,不要这么做
char *p2 = new char;                    //结果未知,不要这么做
char *p3;                                   //结果未知,不要这么做

//在构造函数中使用new时的注意事项
1.空指针可以用NULL与0来表示,C++更倾向于用表示,并用"\0"来表示空字符串
2.应该定义一个复制构造函数,通过一个深度复制将一个对象初始化为另一个对象
3.具体而言,复制构造函数应该new足够的空间来存放复制的数据,而不仅仅复制数据的地址
4.应当定义一个赋值操作符,通过深度复制将一个对象复制给另一个对象

//返回对象
如果被返回的对象是调用函数中的局部变量,则不应按引用或指针来返回,而直接返回对象,因为在被调用函数执行完毕是,局部对象将调用析构函数并销毁
直接返回对象会隐式的调用复制构造函数,并创建一个调用程序能够访问的对象

//再谈new和delete
String *favorite = new String("test");
delete favorite;                              //调用*favorite的析构函数

下述情况时,析构函数将被调用
1 如果对象是动态变量,则执行完定义该对象的程序块时,将调用该对象的析构函数。
2 如果对象是静态变量,则在执行完程序完程序时调用对象的析构函数。
3 如果对象是new创建的,则仅当显式调用delete删除对象时,其析构函数才会被调用

//指针对象小结
1 String *glamour;                         //使用常规的方法来声明指向对象的指针;
2 String *first = &sayings[0];          //将指针初始化为指向自己的指针
3 String *gleep = new String;               //对类使用new将调用相应的类构造函数来初始化新创建的对象
4 使用->操作符通过指针访问类的方法
5 可以使用解引用操作符 * 来获得对象

//布局new操作符
class JT
{
};
char * buffer = new char[BUF];
JT *pc1 = new(buffer)JT;
-JT *pc2 = new(buffer)JT;                    //pc2指向的对象会覆盖pc1指向的对象
-JT *pc2 = new(buffer + sizeof(JT))JT;     //new(buffer)JT 布局new 手动调整
delete pc1;                         //将会调用pc1的析构函数
delete[] buffer;               //不会调用pc1的析构函数,只会释放buffer

//PS
//类方法
类构造函数提供类成员的值
成员初始化列表
Queue::Queue(int qs)
{
     front = rear = NULL;
     items = 0;
     qize = 0;
}
等效
Queue::Queue(int qs) : qize(qs), front(NULL), rear(NULL), items(0)
{
}
只有构造函数可以使用成员初始化列表
并且:
1 const成员
2 引用类成员
class Agent
{
     const int i;
     Agency &belong;
}
必须用初始化列表进行初始化,这是因为其在对象构建成功之前进行初始化
PS:可以在初始化时使用变量,不过在第一次初始化后就不能再改变了 类似于ex:int i = 0; const int j = i;(使用变量来初始化const值)

成员初始化列表的注意事项:
1 这种格式只能用于构造函数
2 必须用这种格式来初始化非静态const数据成员
3 必须用这种格式来初始化引用数据成员
4 数据成员的初始化列表与他们出现在类声明中顺序相同,与成员列表初始化器中的顺序无关

//ticks
class Queue
{
     private:
          Queue(const Queue &q):qsize(0){}                         //私有复制构造函数-不用实现Queue(Queue &q):qsize(0){},两者选其一就可以了
          Queue & operator=(const Queue & q){} (return *this)     //私有赋值函数
}
通过将这两个函数声明为私有了,可以避免滥用的同时而不预先实现
ex:
Queue queue1(queue2);          //错误
queue1 = queue2;               //错误
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值