C++初步(9)
2017-01-23 19:13
351 查看
名空间 (Namespaces)
通过使用名空间(Namespaces)可以将一组全局范围有效的类、对象或函数组织到一个名字下面。换种说法,就是它将全局范围分割成许多子域范围,每个子域范围叫做一个名空间(namespaces)。
使用名空间的格式是:
这里identifier 是一个有效的标示符,namespace-body 是该名空间包含的一组类、对象和函数。例如:
可以使用下面的方法使用。
名空间(namespaces)的作用在于全局对象或函数很有可能重名而造成重复定义的错误,名空间的使用可以避免这些错误的发生。
名空间的使用 (using namespace)
使用 using 指令后面跟namespace可以将当前的嵌套层与一个指定的名空间连在一起,以便使该名空间下定义的对象和函数可以被访问,就好像它们是在全局范围内被定义的一样。它的使用遵循以下原型定义:
别名定义(alias definition)
我们以可以为已经存在的名空间定义别名,格式为:
标准名空间(Namespace std)
针对C++的文件基本上是使用 同样的名字,但没有.h的扩展名,例如, iostream.h 变成了iostream。
如果我们使用ANSI-C++ 兼容的包含文件,我们必须记住所有的函数、类和对象是定义在名空间 std 下面的。
异常处理 (Exception handling)
在编程过程中,很多时候是无法确定一段代码是否总是能够正常工作的,或者因为程序访问了并不存在的资源,或者由于一些变量超出了预期的范围,等等。
这些情况我们统称为异常(引用python的说法),C++ 新近引入的三种操作符能够帮助处理这些出错情况: try, throw 和 catch 。
它们的一般用法是:
它们所进行的操作是:
如下例:
没有捕获的异常 (Exceptions not caught)
如果由于没有对应的类型,一个例外没有被任何catch 语句捕获,特殊函数terminate 将被调用。
这个函数通常已被定义了,以便立即结束当前的进程(process),并显示一个“非正常结束”(“Abnormal termination”)的出错信息。它的格式是:
(怎么用?)
标准异常 (Standard exceptions)
一些C++ 标准语言库中的函数也会扔出一些列外,可以用try 语句来捕获它们。这些异常扔出的参数都是std::exception 引申出的子类类型的。这个类(std::exception) 被定义在C++ 标准头文件 中,用来作为exceptions标准结构的模型。因为这是一个类结构,如果你包括了一个catch 语句块使用地址(reference)来捕获这个结构中的任意一种异常 (也就是说在类型后面加地址符 &),则同时可以捕获所有引申类的异常 (C++的继承原则)。
(你可以用这个标准的例外层次结构来定义的你的例外或从它们引申出新的例外类型。)
类型转换高级 (Advacned Class Type-casting)
ANSI-C++ 标准定义了4种新的类型转换操作符: reinterpret_cast, static_cast, dynamic_cast, const_cast 所有这些操作符都是同样的使用格式:
这里new_type 是要转换成的目标类型,expression 是要被转换的内容。为了便于理解,模仿传统转换操作符,它们的含义是这样的:
reinterpret_cast 可以
-将一个整型指针转换为任意其它类型的指针
-将一个指针转换为一个整型
-反之亦然
这个操作符可以在互不相关的类之间进行指针转换,操作的结果是简单的将一个指针的二进制数据(binary copy)复制到另一个指针。 对指针指向的内容不做任何检查或转换。如果这种复制发生在一个指针到一个整数之间,则对其内容的解释取决于不同的系统,因此任何实现都是不可移植(non portable)的。
static_cast 可以
-执行所有能够隐含执行的类型转换
-以及反向操作(即使这种方向操作是不允许隐含执行的)
用于类的指针,也就是说,它允许将一个引申类的指针转换为其基类类型(这是可以被隐含执行的有效转换),同时也允许进行相反的转换:将一个基类转换为一个引申类类型。
在后面一种情况中,不会检查被转换的基类是否真正完全是目标类型的。例如下面的代码是合法的:
除了能够对类指针进行操作,还可以被用来进行类中明确定义的转换,以及对基本类型的标准转换:
dynamic_cast 完全被用来进行指针的操作。它可以
-用来进行任何可以隐含进行的转换操作
-它们被用于 多态类 情况下的反方向操作。
然而与static_cast不同的是, dynamic_cast 会检查后一种情况的操作是否合法,也就是说它会检查类型转换操作是否会返回一个被要求类型的 有效的完整的对象。这种检查是在程序运行过程中进行的。如果被转换的指针所指向的对象不是一个被要求类型的有效完整的对象,返回值将会是一个空指针NULL 。
如果类型转换被用在引用(reference)类型上,而这个转换不可能进行的话,一个bad_cast 类型的例外(exception)将会被抛出:
const_cast 对常量const 进行设置或取消操作:
其他3种cast 操作符都不可以修改一个对象的常量属性(constness)。
typeid ANSI-C++ 还定义了一个新的操作符叫做 typeid ,它检查一个表达式的类型:
这个操作符返回一个类型为 type_info的常量对象指针,这种类型定义在标准头函数中。这种返回值可以用操作符 == 和 != 来互相进行比较,也可以用来通过name()函数获得一个描述数据类型或类名称的 字符串,例如:
输出: a and b are of different types: a is: class CDummy * b is: class CDummy
预处理指令 (Preprocessor Directives)
预处理指令是写在程序代码中的给预处理器(preprocessor)的命令,而不是程序本身的语句。预处理器在我们编译一个C++程序时由编译器自动执行,它负责控制对程序代码的第一次验证和消化,所有这些指令必须写在单独的一行中,它们不需要加结尾的分号。
#define, 可以被用来生成宏定义常量(defined constantants 或 macros),它的形式是:
#undef, 完成与 #define相反的工作,它取消对传入的参数的宏定义
#ifdef, #ifndef, #if, #endif, #else, #elif
#ifdef 可以使一段程序只有在某个指定常量已经被定义了的情况下才被编译,无论被定义的值是什么。它的操作是:
#ifndef 起相反的作用:在指令#ifndef 和 #endif 之间的代码只有在某个常量没有被定义的情况下才被编译,如:
#if, #else 和 #elif (elif = else if) 用来使得其后面所跟的程序部分只有在特定条件下才被编译。这些条件只能够是常量表达式,如下:
#line 当编译一段程序的时候,如果有错误发生,编译器会在错误前面显示出错文件的名称以及文件中的第几行发生的错误。
-number 是将会赋给下一行的新行数。它后面的行数从这一点逐个递增。
-filename 是一个可选参数,用来替换自此行以后出错时显示的文件名,直到有另外一个#line指令替换它或直到文件的末尾。
例如:
这段代码将会产生一个错误,显示为在文件”assigning variable”, line 1 。
#error 将中断编译过程并返回一个参数中定义的出错信息
例如:
这个例子中,如果 __cplusplus 没有被定义就会中断编译过程。
#include 用指定文件的全部内容替换这条语句。
声明包含一个文件有两种方式:
两种表达的唯一区别是 编译器应该在什么路经下寻找指定的文件。
文件名被写在双引号中,编译器首先在包含这条指令的文件所在的目录下进行寻找,如果找不到指定文件,编译器再到被配置的默认路径下(也就是标准头文件路径下)进行寻找。
如果文件名是在尖括号 <> 中,编译器会直接到默认标准头文件路径下寻找。
#pragma 用来对编译器进行配置的,针对你所使用的平台和编译器而有所不同。要了解更多信息,请参考编译器手册。
如果编译器不支持某个#pragma的特定参数,这个参数会被忽略,不会产生出错。
预定义的宏名称 (Predefined macro names)
以下宏名称在任何时候都是定义好的:
例如:
通过使用名空间(Namespaces)可以将一组全局范围有效的类、对象或函数组织到一个名字下面。换种说法,就是它将全局范围分割成许多子域范围,每个子域范围叫做一个名空间(namespaces)。
使用名空间的格式是:
namespace identifier { namespace-body }
这里identifier 是一个有效的标示符,namespace-body 是该名空间包含的一组类、对象和函数。例如:
namespace general { int a, b; }
可以使用下面的方法使用。
general::a general::b
名空间(namespaces)的作用在于全局对象或函数很有可能重名而造成重复定义的错误,名空间的使用可以避免这些错误的发生。
名空间的使用 (using namespace)
使用 using 指令后面跟namespace可以将当前的嵌套层与一个指定的名空间连在一起,以便使该名空间下定义的对象和函数可以被访问,就好像它们是在全局范围内被定义的一样。它的使用遵循以下原型定义:
using namespace identifier;
别名定义(alias definition)
我们以可以为已经存在的名空间定义别名,格式为:
namespace new_name = current_name ;
标准名空间(Namespace std)
针对C++的文件基本上是使用 同样的名字,但没有.h的扩展名,例如, iostream.h 变成了iostream。
如果我们使用ANSI-C++ 兼容的包含文件,我们必须记住所有的函数、类和对象是定义在名空间 std 下面的。
异常处理 (Exception handling)
在编程过程中,很多时候是无法确定一段代码是否总是能够正常工作的,或者因为程序访问了并不存在的资源,或者由于一些变量超出了预期的范围,等等。
这些情况我们统称为异常(引用python的说法),C++ 新近引入的三种操作符能够帮助处理这些出错情况: try, throw 和 catch 。
它们的一般用法是:
try { // code to be tried throw exception; } catch (type exception) { // code to be executed in case of exception }
它们所进行的操作是:
try语句块中的代码被正常执行。如果有例外发生,代码必须使用关键字throw和一个参数来扔出一个例外。这个参数可以是 *任何有效的数据类型*,它的类型反映了例外的特征。 如果有例外发生,也就是说在try 语句块中有一个throw 指令被执行了,则catch 语句块会被执行,用来捕获throw传来的例外参数。
如下例:
// exceptions #include <iostream.h> int main () { char myarray[10]; try { for (int n=0; n<=10; n++) { if (n>9) throw "Out of range"; //throw 语法与 return相似,只是参数不需要括号。 myarray ='z'; } } /*catch 语句块必须紧跟着try 语句块后面,中间不能有其它的代码。catch 捕获的参数可以是任何有效的数据类型。catch 甚至可以被重载以便能够接受不同类型的参数。*/ catch (char * str) { cout << "Exception: " << str << endl; } return 0; }
/*可以使用多个catch,在这种情况下被执行catch 语句块是相应的符合被throw扔出的参数类型的那一个,就像switch那样*/ // exceptions: multiple catch blocks #include <iostream.h> int main () { try { char * mystring; mystring = new char [10]; if (mystring == NULL) throw "Allocation failure"; for (int n=0; n<=100; n++) { if (n>9) throw n; mystring ='z'; } } catch (int i) { cout << "Exception: "; cout << "index " << i << " is out of range" << endl; } catch (char * str) { cout << "Exception: " << str << endl; } return 0; }
//如下定义一个catch 语句块来捕获所有的异常 try { // code here } catch (...) { cout << "Exception occurred"; }
try { try { // code here } catch (int n) { /*可以使用表达式 `throw;`(不带参数)将里面的catch 语句块捕获的异常传递到外面一层*/ throw; } } catch (...) { cout << "Exception occurred"; }
没有捕获的异常 (Exceptions not caught)
如果由于没有对应的类型,一个例外没有被任何catch 语句捕获,特殊函数terminate 将被调用。
这个函数通常已被定义了,以便立即结束当前的进程(process),并显示一个“非正常结束”(“Abnormal termination”)的出错信息。它的格式是:
void terminate();
(怎么用?)
标准异常 (Standard exceptions)
一些C++ 标准语言库中的函数也会扔出一些列外,可以用try 语句来捕获它们。这些异常扔出的参数都是std::exception 引申出的子类类型的。这个类(std::exception) 被定义在C++ 标准头文件 中,用来作为exceptions标准结构的模型。因为这是一个类结构,如果你包括了一个catch 语句块使用地址(reference)来捕获这个结构中的任意一种异常 (也就是说在类型后面加地址符 &),则同时可以捕获所有引申类的异常 (C++的继承原则)。
// standard exceptions #include <iostream.h> #include <exception> #include <typeinfo> class A {virtual void f() {}; }; int main () { try { A * a = NULL; typeid (*a); } catch (std::exception& e) { /* 这里很像[python的异常处理](http://www.runoob.com/python/python-exceptions.html "菜鸟教程——python异常处理") */ cout << "Exception: " << e.what();//注意使用的格式 } return 0; }
(你可以用这个标准的例外层次结构来定义的你的例外或从它们引申出新的例外类型。)
类型转换高级 (Advacned Class Type-casting)
ANSI-C++ 标准定义了4种新的类型转换操作符: reinterpret_cast, static_cast, dynamic_cast, const_cast 所有这些操作符都是同样的使用格式:
reinterpret_cast <new_type> (expression)//int dynamic_cast <new_type> (expression) static_cast <new_type> (expression) const_cast <new_type> (expression)
这里new_type 是要转换成的目标类型,expression 是要被转换的内容。为了便于理解,模仿传统转换操作符,它们的含义是这样的:
(new_type) expression new_type (expression)
reinterpret_cast 可以
-将一个整型指针转换为任意其它类型的指针
-将一个指针转换为一个整型
-反之亦然
这个操作符可以在互不相关的类之间进行指针转换,操作的结果是简单的将一个指针的二进制数据(binary copy)复制到另一个指针。 对指针指向的内容不做任何检查或转换。如果这种复制发生在一个指针到一个整数之间,则对其内容的解释取决于不同的系统,因此任何实现都是不可移植(non portable)的。
static_cast 可以
-执行所有能够隐含执行的类型转换
-以及反向操作(即使这种方向操作是不允许隐含执行的)
用于类的指针,也就是说,它允许将一个引申类的指针转换为其基类类型(这是可以被隐含执行的有效转换),同时也允许进行相反的转换:将一个基类转换为一个引申类类型。
在后面一种情况中,不会检查被转换的基类是否真正完全是目标类型的。例如下面的代码是合法的:
class Base {}; class Derived: public Base {}; Base * a = new Base; Derived * b = static_cast(a);//子类指针被转化为基类指针
除了能够对类指针进行操作,还可以被用来进行类中明确定义的转换,以及对基本类型的标准转换:
double d=3.14159265; int i = static_cast<int>(d);
dynamic_cast 完全被用来进行指针的操作。它可以
-用来进行任何可以隐含进行的转换操作
-它们被用于 多态类 情况下的反方向操作。
然而与static_cast不同的是, dynamic_cast 会检查后一种情况的操作是否合法,也就是说它会检查类型转换操作是否会返回一个被要求类型的 有效的完整的对象。这种检查是在程序运行过程中进行的。如果被转换的指针所指向的对象不是一个被要求类型的有效完整的对象,返回值将会是一个空指针NULL 。
class Base { virtual dummy(){}; }; class Derived : public Base { }; Base* b1 = new Derived; Base* b2 = new Base; //这个new的用法值得关注 Derived* d1 = dynamic_cast(b1); // succeeds Derived* d2 = dynamic_cast(b2); // fails: returns NULL
如果类型转换被用在引用(reference)类型上,而这个转换不可能进行的话,一个bad_cast 类型的例外(exception)将会被抛出:
class Base { virtual dummy(){}; }; class Derived : public Base { }; Base* b1 = new Derived; Base* b2 = new Base; Derived d1 = dynamic_cast(b1); // succeeds Derived d2 = dynamic_cast(b2); // fails: exception thrown
const_cast 对常量const 进行设置或取消操作:
class C {}; const C * a = new C; C * b = const_cast<C*> (a);
其他3种cast 操作符都不可以修改一个对象的常量属性(constness)。
typeid ANSI-C++ 还定义了一个新的操作符叫做 typeid ,它检查一个表达式的类型:
typeid (expression)
这个操作符返回一个类型为 type_info的常量对象指针,这种类型定义在标准头函数中。这种返回值可以用操作符 == 和 != 来互相进行比较,也可以用来通过name()函数获得一个描述数据类型或类名称的 字符串,例如:
// typeid, typeinfo #include <iostream.h> #include <typeinfo> //头文件 class CDummy { }; int main () { CDummy* a,b; if (typeid(a) != typeid(b)) { cout << "a and b are of different types:\n"; cout << "a is: " << typeid(a).name() << '\n'; cout << "b is: " << typeid(b).name() << '\n'; } return 0; }
输出: a and b are of different types: a is: class CDummy * b is: class CDummy
预处理指令 (Preprocessor Directives)
预处理指令是写在程序代码中的给预处理器(preprocessor)的命令,而不是程序本身的语句。预处理器在我们编译一个C++程序时由编译器自动执行,它负责控制对程序代码的第一次验证和消化,所有这些指令必须写在单独的一行中,它们不需要加结尾的分号。
#define, 可以被用来生成宏定义常量(defined constantants 或 macros),它的形式是:
#define name value
#undef, 完成与 #define相反的工作,它取消对传入的参数的宏定义
#undef name
#ifdef, #ifndef, #if, #endif, #else, #elif
#ifdef 可以使一段程序只有在某个指定常量已经被定义了的情况下才被编译,无论被定义的值是什么。它的操作是:
#ifdef name // code here #endif
#ifndef 起相反的作用:在指令#ifndef 和 #endif 之间的代码只有在某个常量没有被定义的情况下才被编译,如:
#ifndef MAX_WIDTH /*如果当处理到这段代码的时候MAX_WIDTH 还没有被定义,则它会被定义为值100。而如果它已经被定义了,那么它会保持原值 (因为#define 语句这一行不会被执行) 。*/ #define MAX_WIDTH 100 #endif char str[MAX_WIDTH];
#if, #else 和 #elif (elif = else if) 用来使得其后面所跟的程序部分只有在特定条件下才被编译。这些条件只能够是常量表达式,如下:
#if MAX_WIDTH>200 #undef MAX_WIDTH #define MAX_WIDTH 200 #elsif MAX_WIDTH<50 #undef MAX_WIDTH #define MAX_WIDTH 50 #else #undef MAX_WIDTH #define MAX_WIDTH 100 #endif//注意要以此结尾 char str[MAX_WIDTH];
#line 当编译一段程序的时候,如果有错误发生,编译器会在错误前面显示出错文件的名称以及文件中的第几行发生的错误。
#line number "filename"
-number 是将会赋给下一行的新行数。它后面的行数从这一点逐个递增。
-filename 是一个可选参数,用来替换自此行以后出错时显示的文件名,直到有另外一个#line指令替换它或直到文件的末尾。
例如:
#line 1 "assigning variable" int a?;
这段代码将会产生一个错误,显示为在文件”assigning variable”, line 1 。
#error 将中断编译过程并返回一个参数中定义的出错信息
例如:
#ifndef __cplusplus #error A C++ compiler is required #endif
这个例子中,如果 __cplusplus 没有被定义就会中断编译过程。
#include 用指定文件的全部内容替换这条语句。
声明包含一个文件有两种方式:
#include "file" #include <file>
两种表达的唯一区别是 编译器应该在什么路经下寻找指定的文件。
文件名被写在双引号中,编译器首先在包含这条指令的文件所在的目录下进行寻找,如果找不到指定文件,编译器再到被配置的默认路径下(也就是标准头文件路径下)进行寻找。
如果文件名是在尖括号 <> 中,编译器会直接到默认标准头文件路径下寻找。
#pragma 用来对编译器进行配置的,针对你所使用的平台和编译器而有所不同。要了解更多信息,请参考编译器手册。
如果编译器不支持某个#pragma的特定参数,这个参数会被忽略,不会产生出错。
预定义的宏名称 (Predefined macro names)
以下宏名称在任何时候都是定义好的:
macro value __LINE__ 整数值,编译时表示所在行在源文件中的行数。 __FILE__ 字符串,表示被编译的源文件的文件名。 __DATE__ 一个格式为 "Mmm dd yyyy" 的字符串,存储编译开始的日期。 __TIME__ 一个格式为 "hh:mm:ss" 的字符串,存储编译开始的时间。 __cplusplus 整数值,所有C++编译器都定义了这个常量为某个值。如果这个编译器是完全遵守C++标准的,它的值应该等于或大于199711L,具体值取决于它遵守的是哪个版本的标准。
例如:
// 标准宏名称 #include <iostream> using namespace std; int main() { cout << "This is the line number " << __LINE__; cout << " of file " << __FILE__ << ".\n"; cout << "Its compilation began " << __DATE__; cout << " at " << __TIME__ << ".\n"; cout << "The compiler gives a " <<"__cplusplus value of " << __cplusplus; return 0; } This is the line number 7 of file /home/jay/stdmacronames.cpp. Its compilation began Nov 1 2005 at 10:12:29. The compiler gives a __cplusplus value of 1
相关文章推荐
- [转]C++编译初步
- C++面向对象特性实现机制的初步分析 Part2
- 初步学习:Eclipse3.6开发C/C++程序(Windows下)
- 华为的一道初步面试题,面试C++的
- 学习C++的初步感受
- VC7中汇编和C++混合的初步心得
- Microsoft Visual C++虚拟多继承 对象模型初步分析
- Linux环境下的C/C++基础调试技术1——初步了解
- Linux环境下的C/C++基础调试技术1——初步了解
- C++面向对象特性实现机制的初步分析 Part3
- 对于C++的RTTI机制的初步思考
- c++对象模型学习心得(初步继承模型)
- C++课堂(概念提前知)我在QQ群32347435中的讲课内容所涉及到的部分概念 Windows下socket基本流程与初步封装
- 构建自己的C/C++插件开发框架(一)——初步设想
- C++面向对象特性实现机制的初步分析 Part1
- VC7 中汇编和C++混合的初步心得
- 对于C++的RTTI机制的初步思考
- SQLite数据库编程初步(C++)
- [项目部]项目A组(邱震钰)-3D终结者(C++):开发功能文档初步
- c++ 智能指针初步理解