C++ Primer Plus 第十二章知识点(一)

C++ Primer Plus 第十二章知识点简化

  • 1. 动态内存和类
  • 2. 复制构造函数
  • 3. 内容异常
  • Reference:

1. 动态内存和类

归纳点

  • 静态成员变量初始化以及注意点
  • 特殊成员函数

       对于静态数据成员,特别注意的是静态类成员是单独存储的,并不是对象的组成部分,因此,不论创建了多少个对象,程序都只创建一个静态类变量副本,所有的对象共享同一个静态成员,可以参考下面的图:

C++ Primer Plus 第十二章知识点(一)_第1张图片
       具体掌握还是来看看代码:

#ifndef TIME_H_
#define TIME_H_
#include
using namespace std;

class StringBad{
private:
	char* str;                 // pointer to string
	int len;                   // length of string
	static int num_strings;    // number of objects
public:
	StringBad(const char* s);  // constructor
	StringBad();               // default constructor
	~StringBad();              // destructor
	friend ostream& operator<<(ostream& os, const StringBad& st);
};
#endif
#include                     
#include "time.h"

// initializing static class member
int StringBad::num_strings = 0;

StringBad::StringBad(const char* s){
	len = strlen(s);				// set size
	str = new char[len + 1];  		// allot storage
	strcpy(str, s);					// initialize pointer
	num_strings++;                  // set object count
	cout << num_strings << ": \"" << str
		<< "\" object created\n";   
}

StringBad::StringBad(){				// default constructor
	len = 4;
	str = new char[len];
	strcpy(str, "C++");				// default string
	num_strings++;
	cout << num_strings << ": \"" << str
		<< "\" default object created\n";  
}

StringBad::~StringBad(){			// necessary destructor
	cout << "\"" << str << "\" object deleted, ";    
	--num_strings;                  // required
	cout << num_strings << " left\n"; 
	delete[] str;                   // required
}

ostream & operator<<(ostream& os, const StringBad& st){
	os << st.str;
	return os;
}
// vegnews.cpp -- using new and delete with classes
// compile with strngbad.cpp
#include 
#include "time.h"

void callme1(StringBad&);  	// pass by reference
void callme2(StringBad);    // pass by value

int main(){
	{
		cout << "Starting an inner block.\n";
		StringBad headline1("Celery Stalks at Midnight");
		StringBad headline2("Lettuce Prey");
		StringBad sports("Spinach Leaves Bowl for Dollars");
		cout << endl;
		cout << "headline1: " << headline1 << endl;
		cout << "headline2: " << headline2 << endl;
		cout << "sports: " << sports << endl << endl;
		callme1(headline1);
		cout << "headline1: " << headline1 << endl;
		callme2(headline2);
		cout << "headline2: " << headline2 << endl;
		cout << "\nInitialize one object to another:\n";
		StringBad sailor = sports;
		cout << "sailor: " << sailor << endl;
		cout << "Assign one object to another:\n";
		StringBad knot;
		knot = headline1;
		cout << "knot: " << knot << endl;
		cout << "Exiting the block.\n";
	}
	cout << "End of main()\n";
	return 0;
}

void callme1(StringBad& rstr){
	cout << "String passed by reference:\n";
	cout << "    \"" << rstr << "\"\n";
}

void callme2(StringBad str){
	cout << "String passed by value:\n";
	cout << "    \"" << str << "\"\n";
}

输出:
Starting an inner block.
1: "Celery Stalks at Midnight" object created
2: "Lettuce Prey" object created
3: "Spinach Leaves Bowl for Dollars" object created

headline1: Celery Stalks at Midnight
headline2: Lettuce Prey
sports: Spinach Leaves Bowl for Dollars

String passed by reference:
    "Celery Stalks at Midnight"
headline1: Celery Stalks at Midnight
String passed by value:
    "Lettuce Prey"
"Lettuce Prey" object deleted, 2 left
headline2: 葺葺葺葺葺葺葺葺葺葺葺葺QjD?

Initialize one object to another:
sailor: Spinach Leaves Bowl for Dollars
Assign one object to another:
3: "C++" default object created
knot: Celery Stalks at Midnight
Exiting the block.
"Celery Stalks at Midnight" object deleted, 2 left
"Spinach Leaves Bowl for Dollars" object deleted, 1 left
"Spinach Leaves Bowl for Doll8" object deleted, 0 left
"@C" object deleted, -1 left
"-|" object deleted, -2 left

       首先输出的第2-4行可以看出静态成员变量 num_strings是所有创建的object一起共享,如果不是共享的,那2-4行前面的数字就应该都是1;
 
       然后就是理清楚对象创建的顺序,即:headline1 -> headline2 -> sports -> sailor -> knot,那么最后析构的时候就是反着来。那就看输出的导数5行,先删除knot,然后sailor,这两个删除是正常的,然后删除sport的时候发现这个并不是sport完整的内容,Dollars变成了Doll8,到底是哪里导致改变的呢?回到程序发现就执行了StringBad sailor = sports;,那就是这种操作修改了sport,最后被删除的两个对象headline1headline2也是面目全非;
 
       最后计数异常也是个问题,按逻辑最后应该是0 left,分析过来就只能是调用了不将num_string递增的构造函数两次,但程序里定义的两个构造函数都有使num_string递增,那就只能说明该程序使用了第三个构造函数。
 
       StringBad sailor = sports;调用的是哪个构造函数呢?应该是 StringBad(const StringBad&);这个就是 复制构造函数
       
       关于内容为什么出现乱码,我放到最后来讲。

特殊成员函数:

  • 默认构造函数 Stack::Stack(){ }
           带参数的构造函数也可以是默认构造函数,只要所有参数都有默认值,例如Stack() {num = 0;}或者 Stack(int n = 0) {num = n;},这两者只能选其一,不然会有二义性。

  • 默认析构函数;

  • 复制构造函数 StringBad(const StringBad&);

  • 赋值运算符;

  • 地址运算符。
     


2. 复制构造函数

归纳点

  • 何时调用复制构造函数;
  • 显式复制构造函数

①当我们新建一个对象并将其初始化为同类现有对象时被调用;

       假设motto是StringBad对象,那下面四个声明都将调用复制构造函数:

  • StringBad ditto(motto);
  • StringBad metoo = motto;
  • StringBad also = StringBad(motto);
  • StringBad* pStringBad = new StringBad(motto);
     
    默认复制构造函数逐个复制非静态成员,静态成员是保持不变的。

②每当程序生成对象副本时也将调用复制构造函数;

       例如 按值传递 以及 函数返回对象 时也将调用复制构造函数。因为按值传递需要创建一个原始变量的副本,若该原始变量是对象的话,那程序将会调用复制构造函数生成临时对象,引用传递就不会。
 
       对于上面程序 callme2(headline2);就是按值传递。此时用的默认复制构造函数初始化callme2()的形参,除了该处,StringBad sailor = sports;也用到复制构造函数,没有增加num_strings的值,析构却会更新,才导致了 “-2” 的出现。怎么解决这样的问题呢?
 

       采用下面的显式复制构造函数就可以解决:

StringBad::StringBad(const StringBad& s) {
	num_strings++;
	len = strlen(s.str);
	str = new char [len+1];		// allot space
	strcpy(str, s.str);
}

 


3. 内容异常

       输出内容异常那一定就是内存被释放导致的,什么时候被释放的呢?先回顾下释放的顺序knot -> sailor -> sports -> headline2 -> headline1,发现sportsheadline2headline1这三者内容都有问题,逐个分析:

callme2(headline2);导致headline2内存释放;

       按值传递按道理不应该改变原值,这里改变了是怎么回事?首先调用复制构造函数为callme2()生成一个形参,即headline2的副本,此时将headline2的内容传给副本,包括char* str,这是个指针,复制的不是字符串,是个地址,这就危险了,此时副本以及headline2str指针都指向一个地方,那么当这部分程序运行完,副本释放后,headline2str所指向的内存也没了,才导致自身的内容发生改变。

StringBad sailor = sports;导致sports内存释放;

       同理,sportsstr指针和sailor的都指向同一个字符串指针,所以释放完sailor的内存后,sportsstr指向的内存也没了,因此导致乱码。

knot = headline1;导致headline1内存释放。

       与②一样,就不多赘述了。

Reference:

  • 《C++ Primer Plus》第六版 Stephen Prata 著

你可能感兴趣的:(#,C++_Prime_Plus)