您的位置:首页 > 编程语言 > C语言/C++

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

2015-07-30 23:43 190 查看
一.相关知识点

在 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编译器不支持函数外动态声明变量和分配空间,如果要必须是常量值



以上代码仅供参考,如有错误希望大家可以指出,谢谢大家~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: