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

《C++编程思想》 第七章 常 量 (习题+解答)

时间:2015-07-31 01:27:53      阅读:134      评论:0      收藏:0      [点我收藏+]

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

一.相关知识点

       在 C语言中可以选择这样书写:

const bufsize;
       这样写在C++中是不对的,而 C编译器则把它作为一个声明,这个声明指明在别的地方有存储分配。因为C默认const是外部连接的, C++默认const是内部连接的,这样,如果在 C++中想完成与C中同样的事情,必须用extern把连接改成外部连接:
extern const bufsize;//declaration only
这种方法也可用在C语言中。




指向const的指针
       使用指针定义的技巧,正如任何复杂的定义一样,是在标识符的开始处读它并从里向外读。const指定那个“最靠近”的。这样,如果要使正指向的元素不发生改变,我们得写一个像这样的定义:
const int* x;
     从标识符开始,是这样读的:“ x是一个指针,它指向一个const int。”这里不需要初始化,因为说x可以指向任何东西(那是说,它不是一个 const),但它所指的东西是不能被改变的。




const指针
       使指针本身成为一个const指针,必须把const标明的部分放在*的右边,如:
int d=1;
int* const x=&d;
       现在它读成“x是一个指针,这个指针是指向 int的const指针”。因为现在指针本身是const指针,编译器要求给它一个初始化值,这个值在指针寿命期间不变。然而要改变它所指向的值是可以的,可以写*x = 2;




只读存储能力
       如果一个对象被定义成 const对象,它就成为被放进只读存储器(ROM)中的一个候选,这经常是嵌入式程序设计中要考虑的重要事情。然而,只建立一个 const对象是不够的—只读存储能力的条件非常严格。当然,这个对象还应是按位 const的,而不是按成员 const的。如果只通过关键字 mutable实现按成员常量化的话,就容易看出这一点.如果在一个 const成员函数里的const被强制转换了,编译器可能检测不到这个。另外,
1) class或s t r u c t必须没有用户定义的构造函数或析构函数。
2) 这里不能有基类,也不能有包含用户定义的构造函数或析构函数的成员对象。
在只读存储能力类型的const对象中的任何部分上,有关写操做的影响没有定义。虽然适当
形式的对象可被放进ROM里,但是目前还没有什么对象需要放进 ROM里。




可变的( volatile)
       volatile的语法与 const是一样的,但是  volatile的意思是“在编译器认识的范围外,这个数据可以被改变”。不知何故,环境正在改变数据(可能通过多任务处理),所以,  volatile告诉编译器不要擅自做出有关数据的任何假定—在优化期间这是特别重要的。如果编译器说: “我已经把数据读进寄存器,而且再没有与寄存器接触”。一般情况下,它不需要再读这个数据。但是,如果数据是  volatile修饰的,编译器不能作出这样的假定,因为可能被其他进程改变了,它必须重读这个数据而不是优化这个代码。就像建立const对象一样,程序员也可以建立  volatile对象,甚至还可以建立 const volatile对象,这个对象不能被程序员改变,但可通过外面的工具改变。
       volatile的语法与const是一样的,所以经常把它们俩放在一起讨论。为表示可以选择两个中的任何一个,它们俩通称为c-v限定词。



二.相关代码实现

1.

<span style="font-family:SimSun;font-size:18px;"><strong>/*const的安全性(const默认内部连接)
const的作用不限于在常数表达式里代替 #defines。如果用运行期间产生的值初始化
一个变量而且知道在那个变量寿命期内它是不变的,用 const限定该变量,程序设计
中这是一个很好的做法。如果偶然改变了它,编译器会给出一个出错信息。*/
/*SAFECONS.cpp*/
#include <iostream>
using namespace std;

const int i = 100;//typical constant
const int j = i + 10;//value from const expr
long address = (long)&j;//forces storage
char buf[j + 10];//still a const expression

int main()
{
	cout << "type a character & CR:";
	const char c = cin.get();//can't change
	const char c2 = c + 'a';
	cout << c << endl;
	cout << c2 << endl;
	//...
	return 0;
}
</strong></span>


2.

<span style="font-family:SimSun;font-size:18px;"><strong>/*CONSTVAL.cpp*/

/*返回const值
对返回值来讲,存在一个类似的道理,即如果从一个函数中返回值,这个值作为一个常
量:
const int g();
约定了函数框架里的原变量不会被修改。正如前面讲的,返回这个变量的值,因为这
个变量被制成副本,所以初值不会被修改。
首先,这使 const看起来没有什么意义。可以从这个例子中看到:返回常量值明显失
去意义:*/

#include <iostream>
using namespace std;

int f3()
{
	return 1;
}

const int f4()
{
	return 1;
}

int main()
{
	const int j = f3();
	int k = f4();

	return 0;
}</strong></span>


3.

<span style="font-family:SimSun;font-size:18px;"><strong>/*CONSTRET.cpp*/
/*对于内部数据类型来说,返回值是否是常量并没有关系,所以返回一个内部数据类
型的值时,应该去掉const从而使用户程序员不混淆。
处理用户定义的类型时,返回值为常量是很重要的。如果一个函数返回一个类对象的
值,其值是常量,那么这个函数的返回值不能是一个左值(即它不能被赋值,也不能
被修改)。例如:*/

#include <iostream>
using namespace std;

class X
{
	int i;
public:
	X(int I = 0);
	void modify()
	{
		i++;
	}
};

X f5()//f5()返回一个非const X对象
{
	return X();
}

const X f6()//f6()返回一个const X对象
{
	return X();
}

void f7(X &x)//函数f7()把它的参数作为一个非const引用从效果上讲,这与取一个
//非const指针一样,只是语法不同。
{
	x.modify();
}

int main()
{
	//只有非const返回值能作为一个左值使用。换句话说,如果不让对象的返回值
	//作为一个左值使用,当返回一个对象的值时,应该使用const。
	f5() = X(1);
	f5().modify();
	f7(f5());
	//!f6() = X(1);
	//!f6().modify();
	//!f7(f6());
	
	return 0;
}
/*我们可以把一个非const对象的地址赋给一个 const指针,因为也许有时不想改变
某些可以改变的东西。然而,不能把一个const对象的地址赋给一个非 const指针,
因为这样做可能通过被赋值指针改变这个 const指针。当然,总能用类型转换强制
进行这样的赋值,但是,这不是一个好的程序设计习惯,因为这样就打破了对象的
const属性以及由const提供的安全性。*/</strong></span>

4.

<span style="font-family:SimSun;font-size:18px;"><strong>/*传递和返回地址
如果传递或返回一个指针(或一个引用),用户取指针并修改初值是可能的。如果使
这个指针成为常(const)指针,就会阻止这类事的发生,这是非常重要的。事实上,
无论什么时候传递一个地址给一个函数,我们都应该尽可能用 const修饰它,如果不
这样做,就使得带有指向const的指针函数不具备可用性。
是否选择返回一个指向const的指针,取决于我们想让用户用它干什么。下面这个例子
表明了如何使用const指针作为函数参数和返回值:*/
/*CONSTP.cpp*/

#include <iostream>
using namespace std;

void t(int*)//函数t()把一个普通的非const指针作为一个参数
{}

void u(const int* cip)//函数u()把一个const指针作为参数
{
	//!*cip = 2;//illegal--modifies value
	int i = *cip;//OK--copies value
	//可以把信息拷进一个非const变量
	//!int *ip2 = cip;//illegal:non-const
}

const char* v()//函数v()返回一个从串字面值中建立的const char*
{
	return "result of function v()";
}

const int* const w()//w()的返回值要求这个指针及这个指针所指向的对象均为常量
{
	static int i;
	return &i;
}


int main()
{
	int x = 0;
	int* ip = &x;
	const int* cip = &x;
	t(ip);//OK
	//!t(cip);//not OK
	u(ip);//OK
	u(cip);//OK
	//!char* cp = v();//not OK
	const char* ccp = v();//OK
	//!int* ip2 = w();//not OK
	const int* const ccip = w();//OK
	const int* cip2 = w();//OK
	//编译器拒绝把函数w()的返回值赋给一个非const指针,而接受一个const int*
    //const,但令人吃惊的是它也接受一个const int*,这与返回类型不匹配。正
	//如前面所讲的,因为这个值(包含在指针中的地址)正被拷贝,所以自动保持
	//这样的约定:原始变量不能被触动。因此,只有把 const int*const中的第二
	//个 const当作一个左值使用时(编译器会阻止这种情况),它才能显示其意义。
	
	//!*w() = 1;//not OK

	return 0;
}</strong></span>


5.

<span style="font-family:SimSun;font-size:18px;"><strong>/*常量的引用*/
/*CONSTTMP.cpp*/
#include <iostream>
using namespace std;
//临时变量通过引用被传递给一个函数时,这个函数的参数一定是常量(const)引用
class X
{};

X f()
{
	return X();
}

void g1(X&)
{}

void g2(const X&)
{}

int mian()
{
	//!g1(f());//Error:const temporary created by f()
	g2(f());//OK:g2 takes a const reference

	return 0;
}
//函数f()返回类X的一个对象的值。这意味着立即取f()的返回值并把它传递给其他函
//数时(正如g1()和g2()函数的调用),建立了一个临时变量,那个临时变量是常量。
//这样,函数 g1()中的调用是错误的,因为g1()不带一个常量(const)引用,但是
//函数g2()中的调用是正确的。</strong></span>


6.

<span style="font-family:SimSun;font-size:18px;"><strong>/*在一个串指针栈里的 enum的用法*/
/*SSTACK.cpp*/

/*注意push()带一个const char*参数,pop()返回一个const char*, stack保存
const char*。如果不是这样,就不能用 StringStack保存iceCream里的指针。然而,
它不让程序员做任何事情以改变包含在StringStack里的对象。当然,不是所有的串
指针栈都有这个限制。虽然会经常在以前的程序代码里看到使用 enum技术,但C++还
有一个静态常量static const,它在一个类里产生一个更灵活的编译期间的常量。*/

#include <string.h>
#include <iostream.h>

class StringStack
{
	enum{size = 100};
	const char* stack[size];
	int index;
public:
	StringStack();
	void push(const char* s);
	const char* pop();
};


StringStack::StringStack():index(0)
{
	memset(stack, 0 ,size * sizeof(char*));
}

void StringStack::push(const char* s)
{
	if(index < size)
	{
		stack[index++] = s;
	}
}

const char* StringStack::pop()
{
	if(index > 0)
	{
		const char* rv = stack[--index];
		stack[index] = 0;
		return rv;
	}
	return 0;
}

const char* iceCream[] = 
{
	"pralines & cream",
	"fudge ripple",
	"jamocha almond fudge",
	"wild mountain blackberry",
	"raspberry sorbet",
	"lemon swirl",
	"rocky road",
	"deep chocolate fudge"
};

const ICsz = sizeof(iceCream)/sizeof(*iceCream);

int main()
{
	StringStack SS;
	for(int i = 0; i < ICsz; ++i)
	{
		SS.push(iceCream[i]);
	}
	const char* cp;
	while((cp = SS.pop()) != 0)
	{
		cout << cp << endl;
	}

	return 0;
}  </strong></span>


7.

<span style="font-family:SimSun;font-size:18px;"><strong>/*比较const和非const成员函数*/
/*QUOTER.cpp*/
#include <iostream.h>
#include <stdlib.h>
#include <time.h>

class quoter
{
	int lastquote;
public:
	quoter();
	int Lastquote() const;
	const char* quote();
};

quoter::quoter()
{
	lastquote = -1;
	time_t t;
	srand((unsigned)time(&t));//Seed generator
}

int quoter::Lastquote() const
{
	return lastquote;
}

const char* quoter::quote()
{
	static const char* quotes[] = {
		"Are we having fun yet?",
		"Doctors always know best",
		"Is it ... Atomic?",
		"Fear is obscene",
		"There is no scientific evidence"
		"to support the idea"
		"that life is serious",
	};
	const qsize = sizeof(quoters)/sizeof(*quotes);
	int qnum = rand() % qsize;
	while(lastquote >= 0 && qnum == lastquote)
	{
		qnum = rand() % qsize;
	}
	return quotes[lastquote = qnum];
}

int main()
{
	quoter q;
	const quoter cq;
	cq.Lastquote();//OK
	//!cq.quote();//not OK;non-const function
	for(int i = 0; i < 20; ++i)
	{
		cout << q.quote() << endl;
	}

	return 0;
}

/*构造函数和析构函数都不是const成员函数,因为它们在初始化和清理时,总是对对
象作些修改。quote()成员函数也不能是const函数,因为它在返回说明里修改数据成
员lastquote。然而Lastquote()没做修改,所以它可以成为const函数而且也可以被
const对象cq安全地调用。*/</strong></span>


8.

<span style="font-family:SimSun;font-size:18px;"><strong>/*在const成员函数里改变数据成员
第一种方法已成为过去,称为“强制转换 const”.它以相当奇怪的方式执行。取this
(这个关键字产生当前对象的地址)并把它强制转换成指向当前类型对象的指针。看
来this已经是我们所需的指针,但它是一个const指针,所以,还应把它强制转换成
一个普通指针,这样就可以在运算中去掉常量性。*/
/*CASTAWAY.cpp*/
#include <iostream>
using namespace std;

class Y
{
	int i, j;
public:
	Y()
	{
		i = j = 0;
	}
	void f() const;
};

void Y::f() const
{
	//!i++;//Error -- const member function
	((Y*)this)->j++;//OK -- cast away const-ness
}

int main()
{
	const Y yy;
	yy.f();//actually changes it!

	return 0;
}
//问题:this没有用const修饰,这在一个对象的成员函数里被隐藏,这样,如果用户
//不能见到源代码(并找到用这种方法的地方),就不知道发生了什么。</strong></span>


9.

<span style="font-family:SimSun;font-size:18px;"><strong>/*为解决所有这些问题,应该在类声明里使用关键字mutable,以指定一个特定的数据
成员可以在一个 const对象里被改变*/
/*在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将
永远处于可变的状态,即使在一个const函数中。*/
/*MUTABLE.cpp*/
#include <iostream>
using namespace std;

class Y
{
	int i;
	mutable int j;
public:
	Y()
	{
		i = j = 0;
	}
	void f() const;
};

void Y::f()
{
	//!i++;//Error -- const member function
	j++;//OK:mutable
}

int main()
{
	const Y yy;
	yy.f();//actually changes it!

	return 0;
}</strong></span>


10.

<span style="font-family:SimSun;font-size:18px;"><strong>/*类涉及到硬件通信*/
/*VOLATILE.cpp*/
/*就像const一样,我们可以对数据成员、成员函数和对象本身使用 volatile,可以
并且也只能为volatile对象调用volatile成员函数。*/
#include <iostream>
using namespace std;

class comm
{
	const volatile unsigned char byte;
	volatile unsigned char flag;
	enum
	{
		bufsize = 100
	};
	unsigned char buf[bufsize];
	int index;
public:
	comm();
	void isr() volatile;
	char read(int Index) const;
};

comm::comm() :index(0), byte(0), flag(0)
{}

void comm::isr() volatile
{
	if(flag) 
	{
		flag = 0;
	}
	buf[index++] = byte;
	if(index >= bufsize)
	{
		index = 0;
	}
}

char comm::read(int Index) const
{
	if(Index < 0 || Index >= bufsize)
	{
		return 0;
	}
	return buf[Index];
}

int main()
{
	volatile comm Port;
	Port.isr();//OK
	//!Port.read(0);//Not OK read() not volatile

	return 0;
}</strong></span>


三.习题+解答

1. 建立一个具有成员函数 fly ()的名为bird的类和一个不含fly()的名为rock的类。建立一个rock对象,取它的地址,把它赋给一个 void*。现在取这个void *,把它赋给一个bird*,通过那个指针调用函数fly()。 C语言允许公开地通过void*赋值是C语言中的一个“缺陷”,为什么呢?您知道吗?

#include <iostream>
using namespace std;

class bird
{
public:
	bird();
	void fly();
};

class rock
{
public:
	rock();
};

bird::bird()
{}

rock::rock()
{}

void bird::fly()
{
	cout<< "bird can fly!" << endl; 
}

int main()
{
	rock r;
	void* rv= &r;
	bird* b= (bird*)rv;
	b->fly();

	return 0;
}

       标准C语言允许任何非void类型的指针和void类型的指针之间进行直接的相互转换。但在C++中,可以把任何类型的指针直接指派给void类型指针,因为void*是一种通用指针;但是不能反过来将void类型指针直接指派给任何非void类型的指针,除非进行强制转换。因此在C语言环境中我们就可以先把一种具体类型的指针如int*转换为void*类型,然后再把void*类型转换为double*类型,而编译器不会认为这是错误的。然而这种做法确实存在着不易察觉的安全问题(内存扩张和截断等),这是标准C语言的一个缺陷。


2. 建立一个包含 const成员的类,在构造函数初始化表达式表里初始化这个 const成员,建立一个无标记的枚举,用它决定一个数组的大小。

#include <iostream>
using namespace std;

class A
{
	const int i,j;
	enum
	{
		size = 100
	};
	unsigned char arr[size];
public:
	A();
};

A::A():i(0),j(0)
{};

int main()
{
	A a;
	
	return 0;
}


3. 建立一个类,该类具有 const和非const成员函数。建立这个类的 const和非const对象,试着为不同类型的对象调用不同类型的成员函数。

#include <iostream>
#include <string>
using namespace std;

class B
{
	string splay;
public:
	B();
	string play() const;
	const char* go();
};

B::B():splay("happy!")
{
	cout << "Created!" << endl;
}

string B::play() const
{
	return splay;
}

const char* B::go()
{
	static const char* go[] = 
	{
		"playing the computer game",
		"watching TV",
		"playing badminton"
	};
	const qsize = sizeof(go)/sizeof(*go);
	int qnum = rand() % qsize;
	return go[qnum];
}

int main()
{
	B b;
	const B cb;
	cout << b.go() << endl;
	cout << b.play() << endl;
	cout << cb.play() << endl;

	return 0;
}


4. 创建一个函数,这个函数带有一个常量值参数。然后试着在函数体内改变这个参数。

#include <iostream>
using namespace std;

class Table
{
	char c;
	mutable int i;
public:
	Table()
	{
		c = '0';
	    i = 0;
	}
	void f() const;
};

void Table::f() const
{
	i++;//OK:mutable
}

int main()
{
	const Table t;
	t.f();//actually changes it!

	return 0;
}


    为解决所有这些问题,应该在类声明里使用关键字mutable,以指定一个特定的数据成员可以在一个 const对象里被改变在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将
永远处于可变的状态,即使在一个const函数中。


5. 请自行证明C和C++编译器对于const的处理是不同的。创建一个全局的const并将它用于一个常量表达式中;然后在C和C++下编译它。

C

#include <stdio.h>

const int i = 100;
const int j = i + 10;//error C2099: initializer is not a constant

int main()
{
	int num;
	num = 2*j;
	printf("num = %d\n",num);
	
	return 0;
}


如下修改则正确

#include <stdio.h>

int main()
{
	const int i = 100;
    const int j = i + 10;
	int num;
	num = 2*j;
	printf("num = %d\n",num);
	
	return 0;
}



C++

#include <iostream>
using namespace std;

const int i = 100;
const int j = i + 10;

int main()
{
	int num;
	num = 2*j;
	cout << "num = "<< num << endl;
	
	return 0;
}


因为c编译器不支持函数外动态声明变量和分配空间,如果要必须是常量值



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

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

《C++编程思想》 第七章 常 量 (习题+解答)

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

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

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