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

一.相关知识点

        那些通常放在头文件里的名字,像常量、内联函数(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()作为一个中断服务例程来运行。

后期补充。

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

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

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

时间: 2024-11-08 21:27:48

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

Java编程思想---第九章 接口(上)

第九章 接口 接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法. 9.1 抽象类和抽象方法 Java提供一个叫做抽象方法的机制,这个机制是不完整的,仅有声明而没有方法体,抽象方法的语法如下: abstract void f(); 包含抽象方法的类叫做抽象类,如果一个类包含一个或者多个抽象方法,该类必须被限定为抽象的,否则编译器就会报错. 如果一个抽象类不完整,那么当我们试图产生该类的对象时,由于抽象类创建对象时不安全的,所以我们会从编译器那里得到一条出错消息,这样编译器会确保抽象类

C和指针 (pointers on C)——第四章:语句(下)习题解答

题目请见 http://download.csdn.net/download/wangpegasus/5701765 第四章以下通过VS2012 1. #include "stdafx.h" double sqrt(double temp) { double before, after; before = 1.0; after = 1.0; do { before = after; after = (before + temp/before)/2; } while (before !=

java编程思想 第二章

这篇时间较之前篇章时间靠后,是由于,某一天晚上看完Java编程思想文献之后来不及做笔记了. 以下笔记基本为转载,不是原创 第二章   一切都是对象 目录: 2.1 用引用操纵对象 2.2 必须由你创建所有对象 2.3 永远不需要销毁对象 2.4 创建新的数据类型:类 2.5 方法.参数和返回值 2.6 构建一个Java程序 2.7 你的第一个Java程序 2.8 注释和嵌入式文档 2.9 编码风格 2.1 用引用操纵对象 一切都看作对象,操纵的标识符实际上是对象的一个“引用”,遥控器(引用)操纵

《C++编程思想》第八章 内 联 函 数 (知识点+习题+解答)

一.相关知识点 任何在类中定义的函数自动地成为内联函数,但也可以使用inline关键字放在类外定义的函数前面使之成为内联函数.但为了使之有效,必须使函数体和声明结合在一起,否则,编译器将它作为普通函数对待.因此 inline int PlusOne(int x); 没有任何效果,仅仅只是声明函数(这不一定能够在稍后某个时候得到一个内联定义).成功的方法如下: inline int PlusOne(int x) { return ++x ;} 在头文件里,内联函数默认为内部连接--即它是 stat

&lt;java编程思想&gt;第一章读书笔记二

7.伴随多态的可互换对象 前面说了继承,我们知道继承中存在基类(父类)以及导出类(子类),不知道大家有没有遇到过这种情况?就是在向一个方法中传递一个对象作为参数时,我们往往会选择传递一个基类而不是一个子类,为什么要这么做呢?其实原因也很简单,说的高大上一点就是这样做有利于代码的健壮性和可扩展性,说的详细还是有利于代码的健壮性和可扩展性,更重要的也就是可扩展性. 还拿喝可乐的例子来说,如果你传递的参数对象是可乐,那么不管你是给我百事可乐还是可口可乐我都可以接受啊,但是如果你传递的参数仅仅是百事可乐

java编程思想--第二章 一切都是对象

如果不做引申的话,本章没有多少可总结的内容.有些以前没有注意的细节可以写下来. 1.数据存储的位置 (1).寄存器.程序控制不了. (2).堆栈.对象的引用.基本类型. (3).堆.各种对象. (4).常量存储.和代码在一起. (5).非RAM存储.主要指流对象和持久化对象,前者准备网络传输,后者被丢到了硬盘上. 2.java中的数组会被自动初始化: (1).基本类型数组元素会被初始化为0 (2).引用类型数组元素会被初始化为null 3.变量作用域 (1).基本类型的跟C一样 (2).引用类型

Java编程思想 第一章、对象导论

对象导论阅读理解: 1.解决问题的复杂性直接取决于抽象的类型和质量(抽象类型及抽象的是什么?). 汇编语言是对底层机器的轻微抽象,而C.Basic等指令式语言都是对汇编语言的抽象,它们所做的主要抽象仍要求在解决问题时 要基于计算机的结构,而不是基于要解决的问题的结构来考虑,Java语言与C.Basic等相反,其面向问题空间的抽象而非计算 机底层实现来考虑问题结构. 2.解空间及问题空间 解空间:对应于机器模型,问题建模的地方: 问题空间:实际解决问题的模型,问题存在的地方,如一项业务: 3.对象

java编程思想第一章

1.抽象过程Alan kay 总结的面向对象的编程语言: 万物皆为对象. 程序是对象的集合,他们通过发送信息来告诉彼此所要做的. 每个对象都有自己的由其他对象所构成的存储. 每个对象都拥有其类型. 某一特定类型的所有对象都可以接受同样的信息. Booch对对象有一个更简洁的描述:对象是具有状态,行为和标识.这意味着每一个对象哪个都可以拥有内部数据(他们给出了该对象的状态)和方法(它们产生行为),并且每一个对象都可以与其他对象区分开来,就是说每一个对象在内存中有唯一的地址.2.每一个对象都有一个接

学习java编程思想 第一章 对象导论

一.面向对象的五个基本特性: 1.万物皆为对象.将对象视为奇特的变量,他可以存储数据,还可以要求它在自身上执行操作. 2.程序是对象的合集,他们通过发送消息告诉彼此所要做的. 3.每个对象都有自己的由其他对象所构成的存储.换句话说,可以通过创建包含现有对象的包的形式来创建新类型的对象. 4.每个对象否拥有其类型.每个类最重要的特性就是"可以发送什么样的消息给它". 5.某一特定类型的所有对象都可以接受同样的消息. 二.在试图开发或理解一个程序设计时,最好的方法之一就是将对象想象为&qu