码迷,mamicode.com
首页 > 编程语言 > 详细

《C++编程思想》 第十章 引用和拷贝构造函数(知识点+习题+解答)

时间:2015-08-03 21:00:45      阅读:142      评论:0      收藏:0      [点我收藏+]

标签:c++   c++编程思想   c语言   习题   

一.相关知识点

使用引用时有一定的规则:
1) 当引用被创建时,它必须被初始化。(指针则可以在任何时候被初始化。)
2) 一旦一个引用被初始化为指向一个对象,它就不能被改变为对另一个对象的引用。(指针则可以在任何时候指向另一个对象。)
3) 不可能有NULL引用。必须确保引用是和一块合法的存储单元关连。

仅当准备用传值的方式传递类对象时,才需要拷贝构造函数。如果不需要这么做,就不要拷贝构造函数。

对于指向一个对象的指针新的语法变为 ->*,对于一个对象或引用则为.*。


二.相关代码

1.

/*REFRNCE.cpp*/
#include <iostream>
using namespace std;
//对函数f()的调用缺乏使用引用的方便性和清晰性,但很清楚这是传递一个地址。
//在函数 g()的调用中,地址通过引用被传递,但表面上看不出来。
int* f(int* x)
{
	(*x)++;
	return x;
}

int& g(int& x)
{
	x++;
	return x;//safe;outside this scope
}

int& h()
{
	int q;
	//!return q;
	static int x;
	return x;//safe;x lives outside scope
}

int main()
{
	int A = 0;
	f(&A);
	g(A);

	return 0;
}

2.

/*在函数参数中使用常量引用特别重要。这是因为我们的函数也许会接受临时的对象,这个
临时对象是由另一个函数的返回值创立或由函数使用者显式地创立的。临时对象总是不变的,
因此如果不使用常量引用,参数将不会被编译器接受。*/
/*PASCONST.cpp*/
#include <iostream>
using namespace std;
//调用f(1)会产生一个编译错误,这是因为编译器必须首先建立一个引用。即编译器为一个
//int类型分派存储单元,同时将其初始化为 1并为其产生一个地址和引用捆绑在一起。存储的内
//容必须是常量,因为改变它将使之变得没有意义。对于所有的临时对象,必须同样假设它们是
//不可存取的。当改变这种数据的时候,编译器会指出错误,这是非常有用的提示,因为这个改
//变会导致信息丢失。
void f(int&)
{}

void g(const int&)
{}

int main()
{
	//!f(1);//ERROR
	g(1);

	return 0;
}

3.

/*REFPTR.cpp*/
//指针本身增加,而不是指向的内容增加
#include <iostream>
using namespace std;

void increment(int*& i)
{
	i++;
}

int main()
{
	int* i = 0;
	cout << "i = " << i << endl;
	increment(i);
	cout << "i = " << i << endl;

	return 0;
}

4.

/*PASSTRUC.cpp*/
#include <iostream>
using namespace std;
//无法完成预期结果
struct big
{
	char buf[100];
	int i;
	long d;
}B, B2;

big bigfun(big b)
{
	b.i = 100;
	return b;
}

int main()
{
	B2 = bigfun(B);

	return 0;
}

5.

/*一个类在任何时候知道它存在多少个对象*/
/*HOWMANY.cpp*/
#include <fstream.h>
ofstream out("howmany.out");

class howmany
{
	static int object_count;
public:
	howmany()
	{
		object_count++;
	}
	static void print(const char* msg = "")
	{
		if(msg)
		{
			out << msg << ": ";
		}
		out << "object_count = "
			<< object_count << endl;
	}
	~howmany()
	{
		object_count--;
		print("~howmany()");
	}
};

int howmany::object_count = 0;

howmany f(howmany x)
{
	x.print("x argument inside f()");
	return x;
}

int main()
{
	howmany h;
	howmany::print("after construction of h");
	howmany h2 = f(h);
	howmany::print("after call to f()");

	return 0;
}

6.

/*HOWMANY2.cpp*/
#include <fstream.h>
#include <string.h>
ofstream out("howmany2.out");

class howmany2
{
	enum
	{
		bufsize = 30
	};
	char id[bufsize];//字符缓冲器id起着对象识别作用,所以可以
                     //判断被打印的信息是哪一个对象的
	static int object_count;
public:
	howmany2(const char* ID = 0)
	{
		if(ID)
		{
			strncpy(id, ID, bufsize);
			//strncpy()只拷贝一定数目的字符,这是为防止
            //超出缓冲器的限度。
		}
		else
		{
			*id = 0;
		}
		++object_count;
		print("howmany2()");
	}
	howmany2(const howmany2& h)
	//其次是拷贝构造函数howmany2(howmany2&)。拷贝构造函数可以仅从现有的对象创立新
    //对象,所以,现有对象的名字被拷贝给id,id后面跟着单词“copy”,这样我们就能了
	//解它是从哪里拷贝来的。
	{
		strncpy(id, h.id, bufsize);
		strncat(id," copy", bufsize - strlen(id));
		++object_count;
		print("howmany2(howmany2&)");
	}
	void print(const char* msg = 0) const
	//print()函数必须存取具体对象的id数据,所以不再是static成员函数
	{
		if(msg)
		{
			out << msg << endl;
		}
		out << '\t' << id << ": "
			<< "object_count = "
			<< object_count << endl;
	}
	~howmany2()
	{
		--object_count;
		print("~howmany2()");
	}
};

int howmany2::object_count = 0;

howmany2 f(howmany2 x)
{
	x.print("x argument inside f()");
	out << "returning from f()" << endl;
	return x;
}

int main()
{
	howmany2 h("h");
	out << "entering f()" << endl;
	howmany2 h2 = f(h);
	h2.print("h2 after call to f()");
	out << "call f(), no return value" << endl;
	f(h);
	out << "after call to f()" << endl;

	return 0;
}

7.

/*这是预处理器仍然有用的另一个例子,因为 _ FILE_和_LINE_指示仅和预处理器一
起起作用并用在assert( )宏里。假如assert( )宏在一个错误函数里被调用,它仅打
印出错函数的行号和文件名字而不是调用错误函数。这儿显示了使用宏联接(许多是
assert()方法)函数的方法,紧接着调用assert()(程序调试成功后这由一个#define
 NDEBUG消除)。*/
/*ALLEGE.h*/
#ifndef ALLEGE_H_
#define ALLEGE_H_
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

inline void allege_error(int val, const char* msg)
//函数allege_error()有两个参数:一个是整型表达式的值,另一个是这个值为 
//false时需打印的消息
{
	if(!val)
	{
		fprintf(stderr, "error: %s\n", msg);
		//函数fprintf()代替iostreams是因为在只有少量错误的情况下,它工作得
		//更好。假如这不是为调试建立的,exit(1)被调用以终止程序。
#ifdef NDEBUG
		exit(1);
#endif
	}
}

//allege()宏使用三重if-then-else强迫计算表达式expr求值。在宏里调用了
//allege_error(),接着是assert(),所以我们能在调试时获得 assert()的好处
//——因为有些环境紧密地把调试器和assert()结合在一起。
#define allege(expr, msg){	allege_error((expr)?1:0, msg);		assert(expr);}

#define allegemem(expr)	allege(expr, "out of memory")
//allegefile( )宏是allege( )宏用于检查文件的专用版本

#define allegefile(expr)    allege(expr, "could not open file")
//allegemen()宏是allege( )宏用于检查内存的专用版本

#endif 


/*LINENUM.cpp*/
#include <fstream.h>
#include <strstrea.h>
#include <stdlib.h>
#include "allege.h"

int main(int argc, char* argv[])
{
	if(argc < 2)
	{
		cerr << "usge: linenum file\n"
			 << "adds line numbers to file"
			 << endl;
		exit(1);
	}
	strstream text;
	{
		ifstream in(argv[1]);
		allegefile(in);
		text << in.rdbuf();
	}
	ofstream out(argv[1]);
	const bsz = 100;
	char buf[bsz];
	int line = 0;
	while(text.getline(buf, bsz))
	{
		out.setf(ios::right, ios::adjustfield);
		out.width(2);//行号以右对齐方式按2个字段宽打印,所以输出仍然按原来的方式排列
		out << ++line << ")" << buf << endl;
	}

	return 0;
}

8.

/*当编译器为我们的新类创建缺省拷贝构造函数
时编译器干了那些事*/
/*AYTOCC.cpp*/
#include <iostream.h>
#include <string.h>

class withCC
{
public:
	withCC()
	{}
	withCC(const withCC&)
	{
		cout << "withCC(withCC&)" << endl;
	}
};

class woCC
{
	enum
	{
		bsz = 30
	};
	char buf[bsz];
public:
	woCC(const char* msg = 0)
	{
		memset(buf, 0, bsz);
		if(msg)
		{
			strncpy(buf, msg, bsz);
		}
	}
	void print(const char* msg = 0) const
	{
		if(msg)
		{
			cout << msg << ": ";
		}
		cout << buf << endl;
	}
};


class composite
{
	withCC WITHCC;
	woCC WOCC;
public:
	composite():WOCC("composite()"){}
	void print(const char* msg = 0)
	{
		WOCC.print(msg);
	}
};

int main()
{
	composite c;
	c.print("contents of c");
	cout << "calling composite copy-constructor"
		 << endl;
	composite c2 = c;
	c2.print("contents of c2");

	return 0;
}


9.

/*有一个简单的技术防止通过传值方式传递:声明一个私有(private)拷贝构造函数。我们
甚至不必去定义它,除非我们的成员函数或友元(friend)函数需要执行传值方式的传递。如
果用户试图用传值方式传递或返回对象,编译器将会发出一个出错信息。这是因为拷贝构造函
数是私有的。因为我们已显式地声明我们接管了这项工作,所以编译器不再创建缺省的拷贝构
造函数。*/
/*STOPCC.cpp*/
#include <iostream>
using namespace std;

class noCC
{
	int i;
	noCC(const noCC&);
public:
	noCC(int I = 0):i(I){}
};

void f(noCC);

int main()
{
	noCC n;
	//!f(n);
	//!noCC n2 = n;
	//!noCC n3(n);

	return 0;
}


10.

/*PMEM.cpp*/
#include <iostream>
using namespace std;

class widget
{
public:
	void f(int);
	void g(int);
	void h(int);
	void i(int);
};

void widget::h(int){}

int main()
{
	widget w;
	widget* wp = &w;
	void (widget::*pmem)(int) = &widget::h;
	(w.*pmem)(1);
	(wp->*pmem)(2);

	return 0;
}


11.

/*PMEM2.cpp*/
#include <iostream>
using namespace std;

class widget
{
	void f(int) const
	{
		cout << "widget::f()\n";
	}
	void g(int) const
	{
		cout << "widget::g()\n";
	}
	void h(int) const
	{
		cout << "widget::h()\n";
	}
	void i(int) const
	{
		cout << "widget::i()\n";
	}
	enum
	{
		count = 4
	};
	void (widget::*fptr[count])(int) const;
public:
	widget()
	{
		fptr[0] = &widget::f;
		fptr[1] = &widget::g;
		fptr[2] = &widget::h;
		fptr[3] = &widget::i;
	}
	void select(int I, int J)
	{
		if(I < 0 || I >= count)
		{
			return;
		}
		(this->*fptr[I])(J);
	}
	int Count()
	{
		return count;
	}
};


int main()
{
	widget w;
	for(int i = 0; i < w.Count(); ++i)
	{
		w.select(i, 47);
	}

	return 0;
}


三.习题+题解

1. 写一个函数,这个函数用一个 char&作参数并且修改该参数。在 main( )函数里,打印一个char变量,使用这个变量做参数,调用我们设计的函数。然后,再次打印此变量以证明它已被改变。这样做是如何影响程序的可读性的?

#include <iostream>
using namespace std;

void fun(char& c)
{
	c = 'b';
}

int main()
{
	char a = 'a';
	cout << a << endl;
    fun(a);
	cout << a << endl;

	return 0;
}

无法得知是传引用来改变,会误以为是传值改变。


2. 写一个有拷贝构造函数的类,在拷贝构造函数里用cout自我声明。现在,写一个函数,这个函数通过传值方式传入我们新类的对象。写另一个函数,在这个函数内创建这个新类的局部对象,通过传值方式返回这个象。调用这些函数以证明通过传值方式传递和返回对象时,拷贝构造函数确实悄悄地被调用了。

#include <iostream>
using namespace std;

class A
{
public:
	A()
	{}
	A(const A& a)
	{
		cout << "A::A(const A& a)" << endl;
	}
	~A()
	{}
};

void fun(A a)
{}

A func()
{
	A b;
	return b;
}

int main()
{
	A a;
	fun(a);
    func();

	return 0;
}

测试结果:

技术分享

拷贝构造函数确实被调用了。


3. 努力发现如何使得我们的编译器产生汇编语言,并请为PASSTRUC.CPP产生汇编代码。跟踪和揭示我们的编译器为传递和返回大结构产生代码的方法。


产生的汇编代码如下:

技术分享

技术分享


技术分享

版权声明:本文为博主原创文章,未经博主允许不得转载。

《C++编程思想》 第十章 引用和拷贝构造函数(知识点+习题+解答)

标签:c++   c++编程思想   c语言   习题   

原文地址:http://blog.csdn.net/qaz3171210/article/details/47260829

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!