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

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

2015-08-03 12:49 686 查看
一.相关知识点

那些通常放在头文件里的名字,像常量、内联函数(inline function),在缺省情况下都是内部连接的(当然常量只有在C + +中缺省情况下是内部连接的,在 C中它缺省为外部连接)。注意连接只引用那些在连接/装载期间有地址的成员,因此类声明和局部变量并没有连接。



名字空间的产生与一个类的产生非常相似:

namespace MyLib{

//Declarations

}

这就产生了一个新的名字空间,其中包含了各种声明.namespace与class、struct、union和enum有着明显的区别:

1) namespace只能在全局范畴定义,但它们之间可以互相嵌套。

2) 在namespace定义的结尾,右大括号的后面不必要跟一个分号。

3) 一个namespace可以在多个头文件中用一个标识符来定义,就好象重复定义一个类一样。





4) 一个namespace的名字可以用另一个名字来作它的别名,这样我们就不必敲打那些开发商提供的冗长的名字了。





5) 我们不能像类那样去创建一个名字空间的实例。

1. 未命名的名字空间


每个编译单元都可包含一个未命名的名字空间—在namespace关键字后面没有标识符。











在编译单元内,这个空间中的名字自动而无限制地有效。每个编译单元要确保只有一个未命名的名字空间。如果把一个局部名字放在一个未命名的名字空间中,无需加上 static说明就可以让它们作内部连接。

2. 友元

可以在一个名字空间的类定义之内插入一个 friend 声明:

namespace me{

class us{

//...

friend you();

};

}

使用名字空间

可以用两种方法在一个名字空间引用同一个名字:一种是用范围分解运算符,还有一种是用using关键字。

1. 范围分解

名字空间中的任何命名都可以用范围分解运算符明确指定,就像引用一个类中的名字一样.









2. using指令

用using 关键字可以让我们立即输入整个名字空间,摆脱输入一个名字空间中标识符的烦恼。这种using和namespace关键字的搭配使用叫作 using 指令。 using 关键字在当前范围内直接声明了名字空间中的所有的名字,所以可以很方便地使用这些无限制的名字.






现在可以在函数内部声明m a t h中的所有名字,但允许这些名字嵌套在函数中。



using 指令有一个缺点,那就是看起来不那么直观,using指令引入名字可见性的范围是在创建using的地方。但我们可以使来自 using 指令的名字暂时无效,就像它们已经被声明为这个范围的全局名一样。










如果有第二个名字空间




这个名字空间也用 using指令来引入,就可能产生冲突。这种二义性出现在名字的使用时,而不是在using指令使用时。



这样,即使永远不产生二义性,写 using指令引入带名字冲突的名字空间也是可能的。

3. using声明

可以用using声明一次性引入名字到当前范围内。这种方法不像 using指令那样把那些名字当成当前范围的全局名来看待,而是在当前范围之内进行一个声明,这就意味着在这个范围内它可以废弃来自using指令的名字。








using声明给出了标识符的完整的名字,但没有了类型方面的信息。也就是说,如果名字空间中包含了一组用相同名字重载的函数, using声明就声明了这个重载的集合内的所有函数。可以把using声明放在任何一般的声明可以出现的地方。 using声明与普通声明只有一点不同: using声明可以引起一个函数用相同的参数类型来重载(这在一般的重载中是不允许的)。当然这种不确定性要到使用时才表现出来,而不是在声明时。using声明也可以出现在一个名字空间内,其作用与在其他地方时一样:








一个using 声明是一个别名,它允许我们在不同的名字空间声明同样的函数。如果我们不想由于引入不同名字空间的函数而导致重复定义一个函数时,可以用 using声明,它不会引起任何不确定性和重复。



转换连接指定

如果C++中编写一个程序需要用到C库,那该怎么办呢?如果这样声明一个 C函数:

float f(int a,char b);

C++的编译器就会将这个名字变成像 _f_int_int之类的东西以支持函数重载(和类型安全连接)。然而, C编译器编译的库一般不做这样的转换,所以它的内部名为 _f。这样,连接器将无法解决我们C++对f()的调用。

C++中提供了一个连接转换指定,它是通过重载 extern关键字来实现的。 extern后跟一个字符串来指定我们想声明的函数的连接类型,后面是函数声明。

extern "C" float f(int a,char b);

这就告诉编译器 f()是C连接,这样就不会转换函数名。标准的连接类型指定符有“ C”和“ C++”两种,但编译器开发商可选择用同样的方法支持其他语言。

如果我们有一组转换连接的声明,可以把它们放在花括号内:





或在头文件中:





多数C++编译器开发商在他们的头文件中处理转换连接指定,包括 C和C++,所以我们不用担心它们。

虽然标准的C++只支持“C”和“C++”两种连接转换指定,但用同样的方法可以实现对其他语言的支持。



二.相关代码

1.

<span style="font-size:18px;"><strong>/*这是预处理器仍然有用的另一个例子,因为 _ 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
</strong></span>
<span style="font-size:18px;"><strong>#include <iostream>
#include "ALLEGE.h"
using namespace std;

char onechar(const char* string = 0)
{
static const char* s;
if(string)
{
s = string;
return *s;
}
else
{
allege(s, "un-initialized s");
}
if(*s == '\0')
{
return 0;
}
return *s++;
}

char* a = "abcdefghijklmnopqrstuvwxyz";

int main()
{
onechar(a);
char c;
while((c = onechar()) != '\0')
{
cout << c << endl;
}

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


2.


<span style="font-size:18px;"><strong>/*FUNOBJ.cpp*/
#include <iostream>
using namespace std;
//在函数f ( )内部定义一个静态的X类型的对象,它可以用带参数的构造函数来初始化,也可以用
//缺省构造函数。程序控制第一次转到对象的定义点时,而且只有第一次时,才需要执行构造函数。
class X
{
int i;
public:
X(int I = 0):i(I)
{}
~X()
{
cout << "X::~X()" << endl;
}
};

void f()
{
static X x1(47);
static X x2;
}

int main()
{
f();

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


3.


<span style="font-size:18px;"><strong>/*静态对象的析构函数*/
/*STATDEST.cpp*/

#include <fstream.h>
ofstream out("statdest.out");

class obj
{
char c;
public:
obj(char C):c(C)
{
out << "obj::obj() for" << c << endl;
}
~obj()
{
out << "obj::~obj() for" << c << endl;
}
};

obj A('A');

void f()
{
static obj B('B');
}

void g()
{
static obj C('C');
}

int main()
{
out << "inside main()" <<endl;
f();
g();
out << "leaving main()" << endl;
}</strong></span>


4.


<span style="font-size:18px;"><strong>/*一个静态成员的初始化表达式是在一个类的范围内*/
/*STATINIT.cpp*/

#include <iostream>
using namespace std;
//withStatic::限定符把withStatic的范围扩展到全部定义
int x = 100;

class withStatic
{
static int x;
static int y;
public:
void print() const
{
cout << "withStatic::x = " << x << endl;
cout << "withStatic::y = " << y << endl;
}
};

int withStatic::x = 1;
int withStatic::y = x + 1;

int main()
{
withStatic WS;
WS.print();

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


5.


<span style="font-size:18px;"><strong>/*静态数组*/
/*STATARRY.cpp*/
/*对所有的静态数据成员,我们必须提供一个单一的外部定义。这些定义必须有内部
连接,所以可以放在头文件中。初始化静态数组的方法与其他集合类型的初始化一样,
但不能用自动计数。除此之外,在类定义结束时,编译器必须知道足够的类信息来创
建对象,包括所有成员的精确大小。*/
#include <iostream>
using namespace std;

class Values
{
static const int size;
static const float table[4];
static const letters[10];
};

const int Values::size = 100;

const float Values::table[4] = {
1.1, 2.2, 3.3, 4.4
};

const Values::letters[10] = {
'a', 'b', 'c', 'd', 'e',
'f', 'g', 'h', 'i', 'j'
};

int main()
{
return 0;
}
</strong></span>


6.


<span style="font-size:18px;"><strong>/*嵌套类和局部类
可以很容易地把一个静态数据成员放在一个嵌套内中。然而在局部类(在函数内部定
义的类)中不能有静态数据成员*/
/*LOCAL.cpp*/

#include <iostream>
using namespace std;

class outer
{
class inner
{
static int i;//OK
};
};

int outer::inner::i = 47;

void f()
{
class foo
{
public:
//!static int i;
}x;
}

int main()
{
return 0;
}</strong></span>


7.


<span style="font-size:18px;"><strong>/*静态数据成员和静态成员函数在一起使用*/
/*SFUNC.cpp*/

#include <iostream>
using namespace std;
//因为静态成员函数没有 this指针,所以它不能访问非静态的数据成员,也不能调
//用非静态的成员函数,这些函数要用到this指针)。
class X
{
int i;
static int j;
public:
X(int I = 0):i(I)
{
j = i;
}
int val() const
{
return i;
}
static int incr()
{
//!i++:static member function
return +j;
}
static int f()
{
//!val();
return incr();
}
};

int X::j = 0;

int main()
{
X x;
X* xp = &x;
x.f();
xp->f();
X::f();

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


8.


<span style="font-size:18px;"><strong>/*SELFMEN.cpp*/
#include <iostream>
using namespace std;

class egg
{
static egg E;
int i;
egg(int I):i(I){
}
public:
static egg* instance()
{
return &E;
}
int val()
{
return i;
}
};

egg egg::E = 47;

int main()
{
//!egg x(1);
cout << egg::instance()->val() << endl;

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


9.


<span style="font-size:18px;"><strong>/*DEPEND.h*/
#ifndef DEPEND_H_
#define DEPEND_H_
#include <iostream>
using namespace std;

extern int x;
extern int y;

class initializer
{
static int init_count;
public:
initializer()
{
cout << "initializer()" << endl;
if(init_count++ == 0)
{
cout << "performing initialization"
<< endl;
x = 100;
y = 200;
}
}
~initializer()
{
cout << "~initializer()" << endl;
if(--init_count == 0)
{
cout << "performing cleanup" << endl;
}
}
};

static initializer init;

#endif</strong></span>
<span style="font-size:18px;"><strong>/*DEPDEFS.cpp*/
#include "depend.h"

int x;
int y;
int initializer::init_count;</strong></span>
<span style="font-size:18px;"><strong>/*DEPEND.cpp*/
#include "depend.h"

int main()
{
cout << "inside main()" << endl;
cout << "leaving main()" << endl;

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


三.习题+解答

1. 创建一个带整型数组的类。在类内部用未标识的枚举变量来设置数组的长度。增加一个const int 变量,并在构造函数初始化表达式表中初始化。增加一个 static int 成员变量并用特定值来初始化。增加一个内联(inline)构造函数和一个内联(inline)型的print()函数来显示数组中的全部值,并在这两函数内调用静态成员函数。

#include <iostream>
using namespace std;

class A
{
const int i;
static int j;
enum{
SIZE = 100
};
int arr[SIZE];
public:
A(int I = 0):i(I)
{
for(int k = 0; k < SIZE; ++k)
{
arr[k] = incr();
}
};
void print()
{
for(int k = 0; k < SIZE; ++k)
{
cout << arr[k] << " ";
}
cout << endl << endl;
cout << "j = " << incr() << endl;
}
static int incr()
{
++j;
return j;
}
};

int A::j = 47;

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


2. STATDEST.CPP中,在main()内用不同的顺序调用f()、g()来检验构造函数与析构函数的调用顺序,我们的编译器能正确地编译它们吗?

修改代码使其在窗口显示

#include <fstream.h>
ofstream out("statdest.out");

class obj
{
char c;
public:
obj(char C):c(C)
{
cout << "obj::obj() for" << c << endl;
}
~obj()
{
cout << "obj::~obj() for" << c << endl;
}
};

obj A('A');

void f()
{
static obj B('B');
}

void g()
{
static obj C('C');
}

int main()
{
cout << "inside main()" <<endl;
g();
f();
cout << "leaving main()" << endl;
}
结果分析:

(1).





(2).







编译器可以正确编译。

3. 在STATDEST.CPP中,把out的定义变为一个extern声明,并把实际定义放到 A(它的构造函数obj传送信息给out)的定义之后,测试我们的机器是怎样进行缺省错误处理的。当我们运行程序时确保没有其他重要程序在运行,否则我们的机器会出现错误。

程序中断,但是编译连接没有问题。

#include <fstream.h>
extern ofstream out;

class obj
{
char c;
public:
obj(char C):c(C)
{
out << "obj::obj() for" << c << endl;
}
~obj()
{
out << "obj::~obj() for" << c << endl;
}
};

obj A('A');

ofstream out("statdest.out");

void f()
{
static obj B('B');
}

void g()
{
static obj C('C');
}

int main()
{
out << "inside main()" <<endl;
g();
f();
out << "leaving main()" << endl;
}


4. 创建一个类,它的析构函数显示一条信息,然后调用 exit()。创建这个类的一个全局静态对象,看看会发生什么?

#include <iostream>
using namespace std;

class B
{
static int i;
public:
B()
{
cout << "B::B()" << endl;
}
~B()
{
cout << "B::~B()" << endl;
exit(0);
}
};

int B::i = 47;

int main()
{
B b;

return 0;
}


5. 修改第7章的VOLATILE.CPP,使comm::isr()作为一个中断服务例程来运行。

后期补充。
中断服务程序,处理器处理“急件”,可理解为是一种服务,是通过执行事先编好的某个特定的程序来完成的,这种处理“急件”的程序被称为——中断服务程序。

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