《C++编程思想》第四章 初始化与清除(原书代码+习题+解答)

相关代码:

1.

#include <stdio.h>

class tree
{
	int height;
public:
	tree(int initialHeight);
	~tree();
	void grow(int years);
	void printsize();
};

tree::tree(int initialHeight)
{
	height = initialHeight;
}

tree::~tree()
{
	puts("inside tree destructor");
	printsize();
}

void tree::grow(int years)
{
	height += years;
}

void tree::printsize()
{
	printf("tree height is %d\n",height);
}

int main()
{
	puts("before opening brace");
	{
		tree t(12);
		puts("after tree creation");
		t.printsize();
		t.grow(4);
		puts("brfore closing brace");
	}
	puts("after closing brace");

	return 0;
}

2.

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

class G
{
	int i;
public:
	G(int I);
	void show();
};

G::G(int I)
{
	i = I;
}

/*void G::show()
{
	printf("%d\n",i);
}*/

int main()
{
#define SZ 100
	char buf[SZ];//我们能够看到首先是buf被定义,然后是一些语句。然后 x被定义并用一个函数调用对它初
                 //始化。然后y和g被定义
	printf("initialization value?

");
	int retval = (int)gets(buf);
	assert(retval);
	int x = atoi(buf);
	int y = x+3;
	G g(y);
	//g.show();

	return 0;
}

3.

#include <iostream>
using namespace std;

class X
{
public:
	X()
	{}
};

void f(int i)
{
	if(i < 10)
	{
		//!goto jump1;//Error:goto bypasses init
	}
	X x1;
jump1:
	switch(i)
	{
	case 1:
		X x2;
		break;
	//!case 2://Error:case bypasses init
		X x3;//Constructor called here
		break;
	}
}
/*在上面的代码中, goto和switch都可能跳过构造函数的调用点。然而这个对象会在后面的
程序块中起作用,这样,构造函数就没有被调用,所以编译器给出了一条出错信息。这就确保
了对象在产生的同一时候被初始化。*/
int main()
{
	return 0;
}

4.

/*构造函数带多个參数*/
#include <iostream>
using namespace std;

class X
{
	int i,j;
public:
	X(int I, int J)
	{
		i = I;
		j = J;
	}
};

int main()
{
	X xx[] = {X(1,2),X(3,4),X(5,6),X(7,8)};
	/*注意,它看上去就像数组中的每一个对象都对一个没有命名的构造函数调用了一次一样*/
	return 0;
}

5.

#ifndef NESTED_H_
#define NESTED_H_

class stack//这个嵌套 struct 称为 link。它包含指向这个表中的下一个 link 的指针和指向存放在 link 中的数据的指针,假设 next 指针是零,意味着表尾。
{
	struct link/*注意,尽管stack有构造函数与析构函数,但嵌套类 link并没有,这并非说它不须要。

当
                 它被使用时。问题就来了:*/
	{
		void* data;
		link* next;
		void initialize(void* Data, link* Next);
	}*head;
	stack();
	~stack();
	void push(void* Data);
	void* peek();
	void* pop();
};
#endif
#include "nested.h"
#include <stdlib.h>
#include <assert.h>

void stack::link::initialize(void* Data, link* Next)
//简单地两次使用范围分解运算符,以指明这个嵌套 struct 的名字。 stack::link::initialize( ) 函数取參数并把參数赋给它的成员们
{
	data = Data;
	next = Next;
}

stack::stack()
{
	head = 0;
}

void stack::push(void* Data)
//stack::push( ) 取參数,也就是一个指向希望用这个 stack 保存的一块数据的指针。而且把
//这个指针放在栈顶
{
	link* newlink = (link*)malloc(sizeof(link));
	assert(newlink);
	newlink->initialize(Data, head);
	head = newlink;
}

void* stack::peek()
//返回head的值
{
	return head->data;
}

void* stack::pop()
//stack::pop( )取出当前在该栈顶部的 data 指针,然后向下移 head 指针。删除该栈老的栈顶元素
{
	if(head == 0)
	{
		return 0;
	}
	void* result = head->data;
	link* oldHead = head;
	head = head->next;
	free(oldHead);
	return result;
}

stack::~stack()
{
	link* cursor = head;
	while(head)
	{
		cursor = cursor->next;
		free(head->data);
		free(head);
		head = cursor;
	}
}
#include "nested.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

int main(int argc, char** argv)
{
	assert(argc == 2);
	File* file = fopen(argv[1],"r");
	assert(file);
#define BUFSIZE 100
	char buf[BUFSIZE];
	stack textlines;
	while(fgets(buf, BUFSIZE, file))
	{
		char* string = (char*)malloc(strlen(buf)+1);
		assert(string);
		strcpy(string, buf);
		textlines.push(string);
	}
	char* s;
	while((s = (char*)textlines.pop()) != 0 )
	{
		printf("%s",s);
		free(s);
	}
	/*textlines的构造函数与析构函数都是自己主动调用的,所以类的用户仅仅要把精力集中于如何使
用这些对象上。而不须要操心它们是否已被正确地初始化和清除了*/

	return 0;
}

6.

#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
//C++
class stash
{
	int size;                 //Size of each space
	int quantity;             //Number of storage spaces
	int next;                 //Next empty space
	unsigned char* storage;   //storage指针是一个unsigned char*。这是 C 编译器支持的最小的存储片。虽然在某些机器
	                          //上它可能与最大的一般大,这依赖于详细实现。 storage指向的内存从堆中分配
	void inflate(int increase);
	/*inflate()函数使用realloc()为stash得到更大的空间块。

realloc()把已经分配而又希望重分配
	的存储单元首地址作为它的第一个參数(假设这个參数为零。比如 initialize()刚刚被调用时,
	realloc()分配一个新块)。

第二个參数是这个块新的长度,假设这个长度比原来的小,这个块
	将不须要作拷贝。简单地告诉堆管理器剩下的空间是空暇的。假设这个长度比原来的大,在堆
	中没有足够的相临空间。所以要分配新块,而且要拷贝内存。 assert()检查以确信这个操作成
	功。

(假设这个堆用光了, malloc()、 calloc()和realloc()都返回零。)*/
public:
	stash(int Size);          //构造函数
	~stash();                 //析构函数
	int add(void* element);   //add()函数在stash的下一个可用位子上插入一个元素。

首先。它检查是否有可用空间,如
	                          //果没有。它就用后面介绍的 inflate() 函数扩展存储空间。
	void* fetch(int index);   //fetch()首先看索引是否越界,假设没有越界,返回所希望的变量地址。地址的计算採用与
	                          //add()中同样的方法
	int count();              //返回所存储空间大小
};
/*test.cpp*/

/*如果有一个程序设计工具,当创建时它的表现像一个数组,但它的长度能在执行时建
立。我称它为stash*/

#include "test.h"

stash::stash(int Size)
{
	size = Size;
	quantity = 0;
	storage = 0;
	next = 0;
}

stash::~stash()
{
	if(storage)
	{
		puts("freeing storage");
		free(storage);
	}
}

int stash::add(void* element)
{
	if(next >= quantity)
	{
		inflate(100);
	}
	memcpy(&(storage[next * size]),element,size );
	/*我们必须用标准 C 库函数memcpy( )一个字节一个字节地拷贝这个变量。第一个參数是 memcpy()
	開始拷贝字节的目的地址,由以下表达式产生:
	                &(S->storage[S->next * S->size])
	它指示从存储块開始的第 next个可用单元结束。

这个数实际上就是已经用过的单元号加一
	的计数,它必须乘上每一个单元拥有的字节数,产生按字节计算的偏移量。

这不产生地址,而是
	产生处于这个地址的字节,为了产生地址,必须使用地址操作符 &。
	memcpy()的第二和第三个參数各自是被拷贝变量的開始地址和要拷贝的字节数。

n e x t计数
	器加一,并返回被存值的索引。

这样。程序猿能够在后面调用 fetch( )时用它来取得这个元素。*/
	next ++;
	return (next - 1);
}

void* stash::fetch(int index)
{
	if(index >= next || index < 0)
	{
		return 0;
	}
	return &(storage[index * size]);
}

int stash::count()
{
	return next;
}

void stash::inflate( int increase)
{
	void* v = realloc(storage,(quantity + increase)*size );
	assert(v);
	storage = (unsigned char*)v;
	quantity += increase;
}
#include "test.h"
#define BUFSIZE 80

int main()
{
	stash intStash(sizeof(int));
	for(int i = 0;i < 100;++i)
	{
		intStash.add(&i);
	}
	FILE* file = fopen("main.cpp","r");
	assert(file);

	stash stringStash(sizeof(char)*BUFSIZE);
	char buf[BUFSIZE];
	while(fgets(buf, BUFSIZE, file))
	{
		stringStash.add(buf);
	}
	fclose(file);

	for(i = 0;i < intStash.count();++i)
	{
		printf("intStash.fetch(%d) = %d\n",i,
			*(int*)intStash.fetch(i));
	}

	for(i = 0;i < stringStash.count();++i)
	{
		printf("stringStash.fetch(%d) = %s",i,
			(char*)stringStash.fetch(i++));
	}
	putchar(‘\n‘);
	/*再看看cleanup()调用已被取消,但当 intStash和stringStash越出程序块的范围时。析构函数
被自己主动地调用了*/
	return 0;
}

习题+解答

1) 用构造函数与析构函数改动第3章结尾处的HANDLE.H,HANDLE.CPP 和USEHANDL.CPP文件。

#ifndef HANDLE_H_
#define HANDLE_H_

class handle
{
	struct cheshire;//struct cheshire;是一个没有全然指定的类型说明或类声明(一个类的定义包括类的主体)
	cheshire* smile;
public:
	handle();
	~handle();
	int read();
	void change(int);
};
#endif
#include "handle.h"
#include <stdlib.h>
#include <assert.h>

struct handle::cheshire//cheshire 是一个嵌套结构,所以它必须用范围分解符定义struct handle::cheshire {
                       //在handle()中,为cheshire struct分配存储空间在handle::cleanup()中这些空间被释放
{
	int i;
};

handle::handle()
{
	smile = (cheshire*)malloc(sizeof(cheshire));
	assert(smile);
	smile->i = 1;
}

handle::~handle()
{
	free(smile);
}

int handle::read()
{
	return smile->i;
}

void handle::change(int x)
{
	smile->i = x;
}
#include "handle.h"
/*客户程序猿唯一能存取的就是公共的接口部分,因此,仅仅是改动了在实现中的部分,这些
文件就不须又一次编译*/
int main()
{
	handle u;
	u.read();
	u.change(1);

	return 0;
}

2) 创建一个带非缺省构造函数和析构函数的类,这些函数都显示一些信息来表示它们的存在。

写一段代码说明构造函数与析构函数何时被调用。

#include <iostream>
using namespace std;

class A
{
	int i;
	double d;
public:
	A(int I, double D);
	~A();
};

A::A(int I, double D)
{
	i = I;
	d = D;
	cout<<"Create! "<<i<<" "<<d<<endl;
}

A::~A()
{
	cout<<"Delete! "<<i<<" "<<d<<endl;
}

int main()
{
	A a(1,2.0);
	A b(3,4.0);
	A c(5,6.0);
	return 0;
}

3) 用上题中的类创建一个数组来说明自己主动计数与集合初始化。

在这个类中添加一个显示信息的成员函数。

计算数组的大小并逐个訪问它们。调用新成员函数。

#include <iostream>
using namespace std;

class A
{
	int i;
	double d;
public:
	A();
	A(int I, double D);
	~A();
	void show();
};

A::A()
{
	i = 1;
	d = 1.0;
}

A::A(int I, double D)
{
	i = I;
	d = D;
	cout<<"Create! "<<i<<" "<<d<<endl;
}

A::~A()
{
	cout<<"Delete! "<<i<<" "<<d<<endl;
}

void A::show()
{
	cout<<i<<" "<<d<<endl;
}

int main()
{
	A aa[] = {A(1,2),A(3,4),A(5,6)};
	cout<<"sizeof(aa) = "<<sizeof(aa)<<endl;
	for(int i = 0;i < sizeof(aa)/sizeof(aa[0]);++i)
	{
		aa[i].show();
	}
	return 0;
}

4) 创建一个没有不论什么构造函数的类。显示我们能够用缺省的构造函数创建对象。如今为这个类创建一个非缺省的构造函数(带一个參数)。试着再编译一次。

解释发生的现象。

<span style="font-size:18px;">#include <iostream>
using namespace std;

class B
{
	int i;
};

int main()
{
	B b;
	return 0;
}</span>
<span style="font-size:18px;">#include <iostream>
using namespace std;

class B
{
	int i;
public:
	B(int I);
};

B::B(int I)
{
	i = I;
}

int main()
{
	B b(1);//B b;定义会报错。此时仅仅调用定义的构造函数。不调用缺省构造函数
	return 0;
}</span>

以上代码仅供參考。假设有问题希望大家指出~谢谢大家

时间: 2024-10-11 14:06:40

《C++编程思想》第四章 初始化与清除(原书代码+习题+解答)的相关文章

《C++编程思想》 第十三章 继承和组合 (原书代码+习题+解答)

一.相关知识 使用其他人已经创建并调试过的类: 关键是使用类而不是更改已存在的代码.这一章将介绍两种完成这件事的方法.第一种方法是很直接的:简单地创建一个包含已存在的类对象的新类,这称为组合,因为这个新类是由已存在类的对象组合的. 第二种方法更巧妙,创建一个新类作为一个已存在类的类型,采取这个已存在类的形式,对它增加代码,但不修改它.这个有趣的活动被称为继承,其中大量的工作由编译器完成.继承是面向对象程序设计的基石,并且还有另外的含义,将在下一章中探讨. 对于组合和继承(感觉上,它们都是由已存在

《C++编程思想》 第九章 命 名 控 制 (知识点+习题+解答)

一.相关知识点         那些通常放在头文件里的名字,像常量.内联函数(inline function),在缺省情况下都是内部连接的(当然常量只有在C + +中缺省情况下是内部连接的,在 C中它缺省为外部连接).注意连接只引用那些在连接/装载期间有地址的成员,因此类声明和局部变量并没有连接. 名字空间的产生与一个类的产生非常相似: namespace MyLib{ //Declarations } 这就产生了一个新的名字空间,其中包含了各种声明.namespace与class.struct

&lt;JAVA编程思想第四版&gt;——初始化和清除

4.1 用构造器初始化 构造器名称与类名相同,可以使用自变量,无返回值(属于特殊的方法类型) 如果不明确指定类的构造器函数,那么编译器会生成默认构造器. class Inspur { } public class TestConstructor { public static void main(String[] args) { new Inspur();//invoke default constructor } } 但一旦用户指定一个构造器,编译器就不会生成默认构造器(无参构造器). 4.2

《C++编程思想》第四章 初始化与清除(习题+解答)

相关代码: 1. #include <stdio.h> class tree { int height; public: tree(int initialHeight); ~tree(); void grow(int years); void printsize(); }; tree::tree(int initialHeight) { height = initialHeight; } tree::~tree() { puts("inside tree destructor&quo

Java 编程思想 第五章 ----初始化与清理(1)

从今天开始每天一小时的java 编程思想的阅读和编码,其实就是把书上的代码抄下来. 5.5 清理:终结处理和垃圾回收 初始化和清理工作同等重要,但是清理工作却被常常忘记,但是在使用对象之后,对对象弃之不顾的做法并不是很安全.Java有自己的垃圾回收器负责回收无用的对象占据的内存资源.但也有特殊情况:假定你的内存区域不是用new获得的,这是无法用垃圾回收器释放所以java中允许在类中定义一个名为 finalize()的方法.       工作原理: 一旦垃圾回收器准备好释放对象占用的存储空间,将首

Java编程思想---第四章 控制执行流程

第四章  控制执行流程 就像有知觉的生物一样,城西必须在执行过程中控制它的世界并作出选择,在Java中,你要使用执行控制语句来作出选择. 4.1 true和false 所有的条件语句都利用条件表达式的真假来决定执行路径.如a==b,它用操作符==来判断a的值是否等于b的值,返回一个true或false. 4.2 if-else if-else语句是控制程序流程的最基本形式,其中else是可选的,所以可以按下面的两种形式来使用: if(Boolean-expression) statement 或

《C++编程思想》 第十四章 多态和虚函数 (原书代码+习题+讲解)

一.相关知识点 函数调用捆绑 把函数体与函数调用相联系称为捆绑(binding).当捆绑在程序运行之前(由编译器和连接器)完成时,称为早捆绑.我们可能没有听到过这个术语,因为在过程语言中是不会有的:C编译只有一种函数调用,就是早捆绑.上面程序中的问题是早捆绑引起的,因为编译器在只有 instrument地址时它不知道正确的调用函数.解决方法被称为晚捆绑,这意味着捆绑在运行时发生,基于对象的类型.晚捆绑又称为动态捆绑或运行时捆绑.当一个语言实现晚捆绑时,必须有一种机制在运行时确定对象的类型和合适的

Java编程思想 第四章

第四章 目录: 4.1 true和false 4.2 if-else 4.3 迭代 4.4 Foreach语法 4.5 return 4.6 break和continue 4.7 goto 4.8 switch 4.1 true 和 false 注意Java不允许我们将一个数字作为布尔值使用,这与C和C++ 不同(C/C++中,"真"是非零,而"假"是零).如果将数字作为布尔表达式,Java编译器会直接报错. 4.3.1 do-while和while 二者区别在于,

Java编程思想---第五章 初始化与清理(下)

第五章 初始化与清理(下) 5.7 构造器初始化 可以使用构造器来进行初始化,在运行时可以调用方法或执行某些动作来确定初值,但是我们无法阻止自动初始化的进行,它将在构造器被调用之前发生.例如: public class Counter { int i; Counter() { i = 7; } } 那么i首先被置为0,然后变成7.编译器不会强制你一定要在构造器的某个地方或在使用它们之前对元素进行初始化,因为初始化早已得到了保证. 5.7.1 初始化顺序 在类的内部,变量定义的先后顺序决定了初始化