《C++编程思想》第五章 函数重载与缺省参数 (原书代码+习题+解答)

41 篇文章 2 订阅
15 篇文章 0 订阅

一.相关知识点    

    在使用缺省参数时必须记住两条规则。第一,只有参数列表的后部参数才可是缺省的,也就是说,我们不可以在一个缺省参数后面又跟一个非缺省的参数。第二,一旦我们开始使用缺省参数,那么这个参数后面的所有参数都必须是缺省的。(这可以从第一条中导出。)

    在C++中,在函数定义时,我们并不一定需要标识符,像:
void f(int X, int,float f) {/*...*/}

    在函数体中, x和f可以被引用,但中间的这个参数值则不行 ,因为它没有名字。这种调用还必须用一个占位符( placeholder) ,有f(1)或f(1,2,3.0)。这种语法允许我们把一个参数当作占位符而不去用它。其目的在于我们以后可以修改函数定义而不需要修改所有的函数调用。当然,用一个有名字的参数也能达到同样的目的,但如果我们定义的这个参数在函数体内没有使用,多数编译器会给出一条警告信息,并认为我们犯了一个逻辑错误。用这种没有名字的参数就可以防止这种警告产生。更重要的是,如果我们开始用了一个函数参数,而后来发现不需要用它,我们可以高效地将它去掉而不会产生警告错误,而且不需要改动那些调用该函数以前版本的程序代码。

二.本章小结

    函数重载和缺省参数都为调用函数提供了方便。有时为弄清到底哪个函数会被调用,也让人迷惑不清。比如在BitVe ctor类中,下式就似乎对两个bits()函数都可以调用:

int bits(int sz=-1);
    如果调用它时不带参数,函数就会用缺省的值 - 1,它认为我们想知道当前的位数。这种使用似乎同前面的一样,但事实上存在着明显的不同,至少让我们感觉不舒服。在bits()内部我们得按参数的值作一个判断,如果我们必须去找缺省值而不是作为一个普通值,根据这一点,我们就可以形成两个不同的函数。一个是在一般情况下,一个是在缺省情况下。我们也可以把它分割成两个不同的函数体,然后让编译器去选择执行哪一个,这可以提高一点效率,因为不需要传递额外的代码,由条件决定的额外代码也不会被执行。如果我们需要反复调用这个函数,这种效率的少许提高就会表现得很明显。在这种情况下,用缺省参数我们确实会丢失某些东西。首先,缺省值不能作他用,如本例中- 1。现在,我们不能区分一个负数是一个意外还是一个缺省情况。第二,由于在单一参数时只有一个返回值,所以编译器就会丢失很多重载函数时可以得到的有用信息。比如,我们定义:
int i=bv1.set(10);
    编译器会接受它但不再告诉我们其他东西,但作为类的设计者,我们可能认为是一个错误。再看看用户遇到的问题。当用户读我们的头文件时,哪种设计更容易理解呢?缺省值 - 1意味着什么?没有人告诉他们。而用两个分开的函数则非常清楚,因为一个带有一个参数但不返回任何值,而另一个则不带参数但返回一个值。即使没有有关的文档,也很容易猜测这两个函数完成什么功能。
    我们不能把缺省参数作为一个标志去决定执行函数的哪一块,这是基本原则。在这种情况下,只要能够,就应该把函数分解成两个或多个重载的函数。缺省参数应该是能把它当作变通值来处理的值,只不过这个值出现的可能比其他值要大,所以用户可以忽略它或只在需要改变缺省值时才去用它。
    缺省参数的引用是为了使函数调用更容易,特别是当这些函数的许多参数都有特定值时。它不仅使书写函数调用更容易,而且阅读也更方便,尤其当用户是在制定参数过程中,把那些最不可能调整的缺省参数放在参数表的最后面时。缺省参数的一个重要应用是在开始定义函数时用了一组参数,而使用了一段时间后发现要增加一些参数。现在我们只要把这些新增参数都作为缺省的参数,就可以保证所有使用这一函数的代码不会遇到麻烦。

三.相关代码

1.

<span style="font-size:18px;">#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 Size, int InitQuant);
	~stash();                 //析构函数
	int add(void* element);   //add()函数在stash的下一个可用位子上插入一个元素。首先,它检查是否有可用空间,如
	                          //果没有,它就用后面介绍的 inflate() 函数扩展存储空间。
	void* fetch(int index);   //fetch()首先看索引是否越界,如果没有越界,返回所希望的变量地址,地址的计算采用与
	                          //add()中相同的方法
	int count();              //返回所存储空间大小
};
</span>

<span style="font-size:18px;">/*test.cpp*/

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

#include "test.h"

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

stash::stash(int Size, int InitQuant)
//stash()的第一个构造函数与前面一样,但第二个带了一个 Quantity参数来指明分配内存的
//初始大小。在这个定义中,我们可以看到 quantity的内部值与storage指针一起被置零
{
	size = Size;
	quantity = 0;
	next = 0;
	storage = 0;
	inflate(InitQuant);
}

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;
}</span>

<span style="font-size:18px;">#include "test.h"
#define BUFSIZE 80
/*当我们用第一个构造函数时,没有内存分配给 storage ,内存是在第一次调用 add()来增加一
个对象时分配的,另外,执行add()时,当前的内存块不够用时也会分配内存*/
/*我们可以修改这些代码,增加其他参数来调用第二个构造函数。这样我们可以选择 s t a s h的
初始大小*/
int main()
{
	stash intStash(sizeof(int));
/*	stash intStash(sizeof(int),100);*/
	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;
}</span>

2.

<span style="font-size:18px;">#include <stdio.h>
#include <string.h>
#include <assert.h>
#define FSIZE 100
#define TRUE 1
#define FALSE 0
/*位向量类
这里我们进一步看一个操作符重载和缺省参数的例子。考虑一个高效存储真假标志集合的
问题。如果我们有一批数据,这些数据可以用“on”或“off”来表示。用一个叫位向量的类
来存储它们应该是很方便的。有时,位向量并不是作为应用程序的一个工具来使用,而是作为
其他类的一部分。当然对一组标志进行编码,最容易的方法就是每个标志占一个字节*/
class flags
{
	unsigned char f[FSIZE];
public:
	flags();
	void set(int i);
	void clear(int i);
	int read(int i);
	int size();
};

flags::flags()
{
	memset(f, FALSE, FSIZE);
}

void flags::set(int i)
{
	assert(i >= 0 && i < FSIZE);
	f[i] = TRUE;
}

void flags::clear(int i)
{
	assert(i >= 0 && i < FSIZE);
	f[i] = FALSE;
}

int flags::read(int i)
{
	assert(i >= 0 && i < FSIZE);
	return f[i];
}

int flags::size()
{
	return FSIZE;
}

int main()
{
	flags f1;
	for(int i = 0; i < f1.size(); ++i)
	{
		if(i % 3 == 0)
		{
			f1.set(i);
		}
	}
	for(int j = 0; j < f1.size(); ++j)
	{
		printf("f1.read(%d) = %d\n",j,f1.read(j));
	}

	return 0;
}</span>


3.

<span style="font-size:18px;">#ifndef BITVECT_H_
#define BITVECT_H_

class BitVector
{
	unsigned char* bytes;
	int Bits, numBytes;
public:
	BitVector();                  //Default: 0 size
	BitVector(unsigned char* init, int size = 8); 
	//init points to an array of bytes
	//size is measured in bytes
	BitVector(char* binary);      //binary is a string of 1s and 0s
	~BitVector();
	void set(int bit);
	void clear(int bit);
	int read(int bit);
	int bits();                   //number of bits in the vector
	void bits(int size);            //Set number of bits
	void print(const char* msg = "");
};
#endif

/*  第一个构造函数(缺省构造函数)产生了一个大小为零的 BitVector。我们不能在这个向量
中设置任何位,因为它们根本就没有位。首先我们必须用重载过的 bits()函数增加这一矢量的
大小。这个不带参数的版本将返回向量的当前大小,而 bits(int)会把向量的大小改成参数指定
的大小。这样我们可以用同样的函数名来设置和得到向量的大小。注意对新的大小并没有限制—
—我们可以增大它,也可以减少它。
    第二个构造函数要用到一个无符号字符数组的指针,这也是一个原始字节数组,第二个参
数告诉构造函数该数组总共有多少个字节,如果第一个参数是零而不是一个有效的指针,这个
数组被初始化为零。如果我们没有给出第二个参数,其缺省值为 8。我们可能以为我们可以用 
BitVector b(0)这样的声明来产生一个 8个字节的BitVector 对象,并把它们初始化为零。如
果没有第三个构造函数,情况确实如此。
    第三个构造函数取 char* 作为它的唯一的参数。参数 0既可以用于第二个构造函数(第二
个参数缺省)也可用于第三个构造函数。编译器无法知道应该选用哪一个,所以我们会得到一个
含义不清的错误。为了正确地产生这样一个BitVector对象,我们必须将零强制转换成一个适当
的指针: BitVector b((unsigned char*)0)。这的确有些麻烦,所以我们可以选用 BitVector b
产生一个空向量,然后把它们扩展到适当的大小,b.bits(64),这就得到8个字节的向量。*/
</span>


<span style="font-size:18px;">#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "BITVECT.h"
#include <limits.h>//CHAR_BIT = #bits in char
//A byte with the high bit set:

const unsigned char highbit = 
1 << (CHAR_BIT - 1);

BitVector::BitVector()
/*第一个构造函数很简单,就是把所有的变量赋零。*/
{
	numBytes = 0;
	Bits = 0;
	bytes = 0;
}        

BitVector::BitVector(unsigned char* init, int size)
/*第二个构造函数分配内存并初始化位数。
接下来用了一个小技巧。外层的 for循环指示字节数组的下标,内层 for循环每次指
示这个字节的一位,然而这一位是自左向右用 init[index]&(0x80>>offset)计算出来
的。注意这是按位进行与运算的,而且16进制的0x80(最高位为1,其他位为零)右移
offset位,产生一个屏蔽码。如果结果不为零,那么在这一位上一定是 1,这个set()
函数被用来设置BitVector 内部位。注意描述字节位时应从左到右,只有这样用print()
函数显示的结果看上去才是有意义的。*/
{
	numBytes = size;
	Bits = numBytes * CHAR_BIT;
	bytes = (unsigned char*)calloc(numBytes, 1);
	assert(bytes);
	if(init == 0)
	{
		return;
	}
	for(int index = 0; index < numBytes; ++index)
	{
		for(int offset = 0; offset < CHAR_BIT; ++offset)
		{
			if(init[index] & (highbit >> offset))//highbit = 0x80
			{
				set(index * CHAR_BIT + offset);			
			}
		}
	}
}
  
BitVector::BitVector(char* binary)
/*第三个构造函数把一个二进制0,1序列的字符串转换成一个 BitVector。位数就取字
符串的长度。但字符串的长度可能并不正好是 8的整数倍,所以字节数numBytes先将
位数除以8,然后根据余数是否为 0来调整。这种情况下,扫描位是在源串中从左到右
进行的,这与第二个构造函数不同。*/
{
	Bits = strlen(binary);
	numBytes = Bits / CHAR_BIT;
	if(Bits % CHAR_BIT)
	{
		numBytes++;
	}
	bytes = (unsigned char*)calloc(numBytes, 1);
	assert(bytes);
	for(int i = 0; i < Bits; ++i)
	{
		if(binary[i] == '1')
		{
			set(i);
		}
	}
}
   
BitVector::~BitVector()
{
	free(bytes);
}

void BitVector::set(int bit)
{
	assert(bit >= 0 && bit < Bits);
	int index = bit / CHAR_BIT;
	int offset = bit % CHAR_BIT;
	unsigned char mask = (1 << offset);
	bytes[index] |= mask;
	//bytes[bit/CHAR_BIT]|=(1<<(bit % CHAR_BIT));
}

void BitVector::clear(int bit)
{
	assert(bit >= 0 && bit < Bits);
	int index = bit / CHAR_BIT;
	int offset = bit % CHAR_BIT;
	unsigned char mask = ~(1 << offset);
	bytes[index] &= mask;
	//bytes[bit/CHAR_BIT]&=~(1<<(bit % CHAR_BIT));
}

int BitVector::read(int bit)
{
	assert(bit >= 0 && bit < Bits);
	int index = bit / CHAR_BIT;
	int offset = bit % CHAR_BIT;
	unsigned char mask = (1 << offset);
	return bytes[index] & mask;
	//return bytes[bit/CHAR_BIT] & (1<<(bit % CHAR_BIT));
}
/*set()、clear()和read()三个函数形式都差不多,开始三行完全一样: assert()检
查传入的参数是否合法,然后产生指向字节数组的索引和指向被选字节的偏移。set()
和read()用同样的方法产生屏蔽字节:将 1移位到所要的位置。但set()是用选定的字
节与屏蔽字节相“或”来将该位置1,而read()是用选定的字节与屏蔽字节相“与”来
获得该位的状态。clear( )是将1移位到指定位来产生屏蔽字节的,然后将选定的字所
有的位求“反”(用~),再与屏蔽字节相“与”,这样只有指定的位被置为零。*/

int BitVector::bits()
/*第一个仅仅是一个存取函数(一种向没有访问权限的人提供私有成员数据的函数)
,告知数组中共有多少位。*/
{
	return Bits;
}
                 
void BitVector::bits(int size)
/*第二个函数用它的参数来计算所
需的字节数,然后用realloc()函数重新分配内存(如果bytes为零,它将分配新内存
),并对新增的位置零。注意,如果我们要求的位数与原有的位数相等,这个函数仍
有可能重新分配内存(这取决于realloc()函数的实现)。但这不会破坏任何东西。*/
{
	int oldsize = Bits;
	Bits = size;
	numBytes = Bits / CHAR_BIT;
	if(Bits % CHAR_BIT)
	{
		numBytes++;
	}
	void* v = realloc(bytes, numBytes);
	assert(v);
	bytes = (unsigned char*)v;
	for(int i = oldsize; i < Bits; ++i)
	{
		clear(i);
	}
}
            
void BitVector::print(const char* msg)
/*print()函数显示msg字串,标准的 C库函数puts()已经加了一个新行,所以对缺省
参数将输出一个新行。然后它用read()读取每一位的值以确定显示什么字符。为了阅
读方便,在每读完 8位后它显示一个空格。由于第二个 BitVector构造函数是读取字
节数组的方式, print()函数将会用熟悉的形式显示结果。*/
{
	puts(msg);
	for(int i = 0; i < Bits; ++i)
	{
		if(read(i))
		{
			putchar('1');
		}
		else
		{
			putchar('0');
		}
		if((i+1) % CHAR_BIT == 0)
		{
			putchar(' ');
		}
	}
	putchar('\n');
}</span>


<span style="font-size:18px;">#include "BITVECT.h"

int main()
{
	unsigned char b[] = {
	0x0d, 0xff, 0xf0,
	0xAA, 0x78, 0x11
	};
	BitVector bv1(b, sizeof(b)/sizeof(*b));
	BitVector bv2("10010100111100101010001010010010101");
	bv1.print("bv1 before modification");
	for(int i = 36; i < bv1.bits(); ++i)
	{
		bv1.clear(i);
	}
	bv1.print("bv1 after modification");
	bv2.print("bv2 before modification");
	for(int j = bv2.bits()-10; j < bv2.bits(); ++j)
	{
		bv2.clear(j);
	}
	bv2.set(30);
	bv2.print("bv2 after modification");
	bv2.bits(bv2.bits() / 2);
	bv2.print("bv2 cut in half");
	bv2.bits(bv2.bits() + 10);
	bv2.print("bv2 grown by 10");
	BitVector bv3((unsigned char*)0);
    /*在程序的尾部,bv2被减少了一半然后又增大,用以说明将BitVector的尾部置零的
	一种方法。*/

	return 0;
}</span>


四.习题+解答

1) 创建一个 message类,其构造函数带有一个 char*型的缺省参数。创建一个私有成员char*,并假定构造函数可以传递一个静态引用串:简单将指针参数赋给内部指针。创建两个重载的成员函数 print() ;一个不带参数,而只是显示存储在对象中的消息,另一个带有 char*参数,它将显示该字符串加上对象内部消息。比较这种方法和使用构造函数的方法,看哪种方法更合理?

a.该方法:

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

class message
{
	char* name;
public:
	message(char* msg= "");
	void print();
	void print(char* str= "");
};


message::message(char* msg)
{
	name = msg;
}

void message::print()
{
	cout<<name<<endl;
}

void message::print(char* str)
{
	cout<<str<<" "<<name<<endl;
}


int main()
{
	message m("Over!");
	//m.print();调用时产生歧义
	char* s = "Game";
	m.print(s);

	return 0;
}
</span>


b.构造函数

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

class message
{
	char* name;
public:
	message(char* msg = "",char* str = "");
};

message::message(char* msg,char* str)
{
	name = msg;
	cout<<str<<" "<<name<<endl;
}

int main()
{
	message m("Over","Game");

	return 0;
}</span>


我认为每个方法都有自己的优缺点:方法a确保了对对象的初始化和输出分开进行,但是由于函数重载使得函数调用发生歧义;方法b不需要专门的输出,但是这样看起来逻辑层次感不强。

2) 测定您的编译器是怎样产生汇编输出代码的,并尝试着减小名字分解表。

名字分解表编译器编译时对程序里的标识符(变量名,类名,函数名之类)所作出的一个映射表。

不同编译器不同。(其余不了解)


3) 用缺省参数修改STASH4.H和STASH4.CPP中的构造函数,创建两个不同的stash对象来测试构造函数。

<span style="font-size:18px;">#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 Size, int InitQuant = 100);
	~stash();                 //析构函数
	int add(void* element);   //add()函数在stash的下一个可用位子上插入一个元素。首先,它检查是否有可用空间,如
	                          //果没有,它就用后面介绍的 inflate() 函数扩展存储空间。
	void* fetch(int index);   //fetch()首先看索引是否越界,如果没有越界,返回所希望的变量地址,地址的计算采用与
	                          //add()中相同的方法
	int count();              //返回所存储空间大小
};
</span>

<span style="font-size:18px;">/*test.cpp*/

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

#include "test.h"

/*stash::stash(int Size)
{
	size = Size; 
	quantity = 0;
	storage = 0; 
	next = 0;    
}
*/
stash::stash(int Size, int InitQuant)
//stash()的第一个构造函数与前面一样,但第二个带了一个 Quantity参数来指明分配内存的
//初始大小。在这个定义中,我们可以看到 quantity的内部值与storage指针一起被置零
{
	size = Size;
	quantity = 0;
	next = 0;
	storage = 0;
	inflate(InitQuant);
}

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;
}</span>

<span style="font-size:18px;">#include "test.h"
#define BUFSIZE 80
/*当我们用第一个构造函数时,没有内存分配给 storage ,内存是在第一次调用 add()来增加一
个对象时分配的,另外,执行add()时,当前的内存块不够用时也会分配内存*/
/*我们可以修改这些代码,增加其他参数来调用第二个构造函数。这样我们可以选择 s t a s h的
初始大小*/
int main()
{
	stash intStash(sizeof(int));
	for(int i = 0;i < 100;++i)
	{
		intStash.add(&i);
	}
	stash intStashy(sizeof(int),20);
	for(i = 0;i < 100;++i)
	{
		intStashy.add(&i);
	}
	for(i = 0;i < intStash.count();++i)
	{
		printf("intStash.fetch(%d) = %d\n",i,
			*(int*)intStash.fetch(i));
	}
	for(i = 0;i < intStashy.count();++i)
	{
		printf("intStashy.fetch(%d) = %d\n",i,
			*(int*)intStashy.fetch(i));
	}
	putchar('\n');

	return 0;
}</span>


4) 比较flags类与BitVector类的执行速度。为了保证不会与效率弄混,把set()、clear()和read()中的index、

offset和mask定义合并成一个单一的声明来完成适当的操作(测试这个新的代码以确保代码正确)。

flags类:

<span style="font-size:18px;">#include <stdio.h>
#include <string.h>
#include <assert.h>
#define FSIZE 100
#define TRUE 1
#define FALSE 0
/*位向量类
这里我们进一步看一个操作符重载和缺省参数的例子。考虑一个高效存储真假标志集合的
问题。如果我们有一批数据,这些数据可以用“on”或“off”来表示。用一个叫位向量的类
来存储它们应该是很方便的。有时,位向量并不是作为应用程序的一个工具来使用,而是作为
其他类的一部分。当然对一组标志进行编码,最容易的方法就是每个标志占一个字节*/
class flags
{
	unsigned char f[FSIZE];
public:
	flags();
	void set(int i);
	void clear(int i);
	int read(int i);
	int size();
};

flags::flags()
{
	memset(f, FALSE, FSIZE);
}

void flags::set(int i)
{
	assert(i >= 0 && i < FSIZE);
	f[i] = TRUE;
}

void flags::clear(int i)
{
	assert(i >= 0 && i < FSIZE);
	f[i] = FALSE;
}

int flags::read(int i)
{
	assert(i >= 0 && i < FSIZE);
	return f[i];
}

int flags::size()
{
	return FSIZE;
}

int main()
{
	flags f1;
	for(int i = 0; i < f1.size(); ++i)
	{
		if(i % 3 == 0)
		{
			f1.set(i);
		}
	}
	for(int j = 0; j < f1.size(); ++j)
	{
		printf("f1.read(%d) = %d\n",j,f1.read(j));
	}

	return 0;
}</span>
显示结果:



这样很浪费存储空间,因为我们用了八位来表示一个只要一位就可表示的标志。

BitVector类:

<span style="font-size:18px;">#ifndef BITVECT_H_
#define BITVECT_H_

class BitVector
{
	unsigned char* bytes;
	int Bits, numBytes;
public:
	BitVector();                  //Default: 0 size
	BitVector(unsigned char* init, int size = 8); 
	//init points to an array of bytes
	//size is measured in bytes
	BitVector(char* binary);      //binary is a string of 1s and 0s
	~BitVector();
	void set(int bit);
	void clear(int bit);
	int read(int bit);
	int bits();                   //number of bits in the vector
	void bits(int size);            //Set number of bits
	void print(const char* msg = "");
};
#endif

/*  第一个构造函数(缺省构造函数)产生了一个大小为零的 BitVector。我们不能在这个向量
中设置任何位,因为它们根本就没有位。首先我们必须用重载过的 bits()函数增加这一矢量的
大小。这个不带参数的版本将返回向量的当前大小,而 bits(int)会把向量的大小改成参数指定
的大小。这样我们可以用同样的函数名来设置和得到向量的大小。注意对新的大小并没有限制—
—我们可以增大它,也可以减少它。
    第二个构造函数要用到一个无符号字符数组的指针,这也是一个原始字节数组,第二个参
数告诉构造函数该数组总共有多少个字节,如果第一个参数是零而不是一个有效的指针,这个
数组被初始化为零。如果我们没有给出第二个参数,其缺省值为 8。我们可能以为我们可以用 
BitVector b(0)这样的声明来产生一个 8个字节的BitVector 对象,并把它们初始化为零。如
果没有第三个构造函数,情况确实如此。
    第三个构造函数取 char* 作为它的唯一的参数。参数 0既可以用于第二个构造函数(第二
个参数缺省)也可用于第三个构造函数。编译器无法知道应该选用哪一个,所以我们会得到一个
含义不清的错误。为了正确地产生这样一个BitVector对象,我们必须将零强制转换成一个适当
的指针: BitVector b((unsigned char*)0)。这的确有些麻烦,所以我们可以选用 BitVector b
产生一个空向量,然后把它们扩展到适当的大小,b.bits(64),这就得到8个字节的向量。*/
</span>

<span style="font-size:18px;">#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "BITVECT.h"
#include <limits.h>//CHAR_BIT = #bits in char
//A byte with the high bit set:

const unsigned char highbit = 
1 << (CHAR_BIT - 1);

BitVector::BitVector()
/*第一个构造函数很简单,就是把所有的变量赋零。*/
{
	numBytes = 0;
	Bits = 0;
	bytes = 0;
}        

BitVector::BitVector(unsigned char* init, int size)
/*第二个构造函数分配内存并初始化位数。
接下来用了一个小技巧。外层的 for循环指示字节数组的下标,内层 for循环每次指
示这个字节的一位,然而这一位是自左向右用 init[index]&(0x80>>offset)计算出来
的。注意这是按位进行与运算的,而且16进制的0x80(最高位为1,其他位为零)右移
offset位,产生一个屏蔽码。如果结果不为零,那么在这一位上一定是 1,这个set()
函数被用来设置BitVector 内部位。注意描述字节位时应从左到右,只有这样用print()
函数显示的结果看上去才是有意义的。*/
{
	numBytes = size;
	Bits = numBytes * CHAR_BIT;
	bytes = (unsigned char*)calloc(numBytes, 1);
	assert(bytes);
	if(init == 0)
	{
		return;
	}
	for(int index = 0; index < numBytes; ++index)
	{
		for(int offset = 0; offset < CHAR_BIT; ++offset)
		{
			if(init[index] & (highbit >> offset))//highbit = 0x80
			{
				set(index * CHAR_BIT + offset);			
			}
		}
	}
}
  
BitVector::BitVector(char* binary)
/*第三个构造函数把一个二进制0,1序列的字符串转换成一个 BitVector。位数就取字
符串的长度。但字符串的长度可能并不正好是 8的整数倍,所以字节数numBytes先将
位数除以8,然后根据余数是否为 0来调整。这种情况下,扫描位是在源串中从左到右
进行的,这与第二个构造函数不同。*/
{
	Bits = strlen(binary);
	numBytes = Bits / CHAR_BIT;
	if(Bits % CHAR_BIT)
	{
		numBytes++;
	}
	bytes = (unsigned char*)calloc(numBytes, 1);
	assert(bytes);
	for(int i = 0; i < Bits; ++i)
	{
		if(binary[i] == '1')
		{
			set(i);
		}
	}
}
   
BitVector::~BitVector()
{
	free(bytes);
}

void BitVector::set(int bit)
{
	assert(bit >= 0 && bit < Bits);
	bytes[bit / CHAR_BIT]|=(1<<(bit % CHAR_BIT));
}

void BitVector::clear(int bit)
{
	assert(bit >= 0 && bit < Bits);
	bytes[bit/CHAR_BIT]&=~(1<<(bit % CHAR_BIT));
}

int BitVector::read(int bit)
{
	assert(bit >= 0 && bit < Bits);
	return bytes[bit/CHAR_BIT] & (1<<(bit % CHAR_BIT));
}
/*set()、clear()和read()三个函数形式都差不多,开始三行完全一样: assert()检
查传入的参数是否合法,然后产生指向字节数组的索引和指向被选字节的偏移。set()
和read()用同样的方法产生屏蔽字节:将 1移位到所要的位置。但set()是用选定的字
节与屏蔽字节相“或”来将该位置1,而read()是用选定的字节与屏蔽字节相“与”来
获得该位的状态。clear( )是将1移位到指定位来产生屏蔽字节的,然后将选定的字所
有的位求“反”(用~),再与屏蔽字节相“与”,这样只有指定的位被置为零。*/

int BitVector::bits()
/*第一个仅仅是一个存取函数(一种向没有访问权限的人提供私有成员数据的函数)
,告知数组中共有多少位。*/
{
	return Bits;
}
                 
void BitVector::bits(int size)
/*第二个函数用它的参数来计算所
需的字节数,然后用realloc()函数重新分配内存(如果bytes为零,它将分配新内存
),并对新增的位置零。注意,如果我们要求的位数与原有的位数相等,这个函数仍
有可能重新分配内存(这取决于realloc()函数的实现)。但这不会破坏任何东西。*/
{
	int oldsize = Bits;
	Bits = size;
	numBytes = Bits / CHAR_BIT;
	if(Bits % CHAR_BIT)
	{
		numBytes++;
	}
	void* v = realloc(bytes, numBytes);
	assert(v);
	bytes = (unsigned char*)v;
	for(int i = oldsize; i < Bits; ++i)
	{
		clear(i);
	}
}
            
void BitVector::print(const char* msg)
/*print()函数显示msg字串,标准的 C库函数puts()已经加了一个新行,所以对缺省
参数将输出一个新行。然后它用read()读取每一位的值以确定显示什么字符。为了阅
读方便,在每读完 8位后它显示一个空格。由于第二个 BitVector构造函数是读取字
节数组的方式, print()函数将会用熟悉的形式显示结果。*/
{
	puts(msg);
	for(int i = 0; i < Bits; ++i)
	{
		if(read(i))
		{
			putchar('1');
		}
		else
		{
			putchar('0');
		}
		if((i+1) % CHAR_BIT == 0)
		{
			putchar(' ');
		}
	}
	putchar('\n');
}</span>

<span style="font-size:18px;">#include "BITVECT.h"

int main()
{
	unsigned char b[] = {
	0x0d, 0xff, 0xf0,
	0xAA, 0x78, 0x11
	};
	BitVector bv1(b, sizeof(b)/sizeof(*b));
	BitVector bv2("10010100111100101010001010010010101");
	bv1.print("bv1 before modification");
	for(int i = 36; i < bv1.bits(); ++i)
	{
		bv1.clear(i);
	}
	bv1.print("bv1 after modification");
	bv2.print("bv2 before modification");
	for(int j = bv2.bits()-10; j < bv2.bits(); ++j)
	{
		bv2.clear(j);
	}
	bv2.set(30);
	bv2.print("bv2 after modification");
	bv2.bits(bv2.bits() / 2);
	bv2.print("bv2 cut in half");
	bv2.bits(bv2.bits() + 10);
	bv2.print("bv2 grown by 10");
	BitVector bv3((unsigned char*)0);
    /*在程序的尾部,bv2被减少了一半然后又增大,用以说明将BitVector的尾部置零的
	一种方法。*/

	return 0;
}</span>

显示结果:


可知我们用了一位来表示一位就可表示的标志。

明显后者比前者效率高。


5) 修改FLAGS.CPP以使它可以动态地为标志分配内存,传给构造函数的参数是空间存储的大小,其缺省值为100。保证在析构函数中清除这些存储空间。

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

#define TRUE 1
#define FALSE 0
/*位向量类
这里我们进一步看一个操作符重载和缺省参数的例子。考虑一个高效存储真假标志集合的
问题。如果我们有一批数据,这些数据可以用“on”或“off”来表示。用一个叫位向量的类
来存储它们应该是很方便的。有时,位向量并不是作为应用程序的一个工具来使用,而是作为
其他类的一部分。当然对一组标志进行编码,最容易的方法就是每个标志占一个字节*/

class flags
{
	unsigned char *f;
	int useSize;
public:
	flags(int Size = 100);
	~flags();
	void set(int i);
	void clear(int i);
	int read(int i);
	int size();
};

flags::flags(int Size)
{
	useSize = Size;
	unsigned char* v = (unsigned char*)malloc
		(sizeof(unsigned char)*useSize);
	assert(v);
	f = v;
	memset(f, FALSE, useSize);
	/*memset(f, FALSE, FSIZE);*///memset是计算机中C/C++语言函数。将s所指向的某
	//一块内存中的前n个 字节的内容全部设置为ch指定的ASCII值,块的大小由第三
	//个参数指定,这个函数通常为新申请的内存做初始化工作, 其返回值为指向s
	//的指针。
}

flags::~flags()
{
	free(f);
}

void flags::set(int i)
{
	assert(i >= 0 && i < useSize);
	f[i] = TRUE;
}

void flags::clear(int i)
{
	assert(i >= 0 && i < useSize);
	f[i] = FALSE;
}

int flags::read(int i)
{
	assert(i >= 0 && i < useSize);
	return f[i];
}

int flags::size()
{
	return useSize;
}

int main()
{
	flags f1;
	for(int i = 0; i < f1.size(); ++i)
	{
		if(i % 3 == 0)
		{
			f1.set(i);
		}
	}
	for(int j = 0; j < f1.size(); ++j)
	{
		printf("f1.read(%d) = %d\n",j,f1.read(j));
	}

	return 0;
}


以上代码仅供参考,如果有错误的地方,希望大家可以指出,谢谢大家~




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值