1. C++ 没有明白定义怎样释放指向不是用new 分配的内存地址的指针。以下提供了一些安全的和不安全的delete expressions 表达式。
int i; 242 int *pi = &i; string str ="dwarves"; double *pd = new double(33); delete str; // error: str is not a dynamic object delete pi; // error: pi refers to a local delete pd; // ok
2. 零值指针的删除 假设指针的值为0。则在其上做delete 操作是合法的,但这样做没有不论什么意义:
int *ip = 0; delete ip; // ok: always ok to delete a pointer that is equal to 0
C++ 保证:删除0 值的指针是安全的。
3. 下列语句哪些(假设有的话)是非法的或错误的?
vector<string> svec22(10); vector<string> *pvec1 = new vector<string>(10); vector<string> *pvec2 = new vector<string>[10]; // X vector<string> *pv1 = &svec22; vector<string> *pv2 = pvec1; delete svec22; // Compile issue delete pvec1; delete [] pvec2; delete pv1; // It is not a dynamic object delete pv2; // It has been delete
4. 理解算术转换
研究大量例题是帮助理解算术转换的最好方法。以下大部分例题中,要么是将操作数转换为表达式中的最大类型,要么是在赋值表达式中将右操作数转换为左操作数的类型。
bool flag; char cval; short sval; unsignedshort usval; int ival; unsignedint uival; long lval; unsignedlong ulval; float fval; doubledval; 3.14159L + 'a'; //promote 'a' to int, then convert to long double dval + ival; // ival converted to double dval + fval; // fval converted to double ival = dval; // dval converted (by truncation) toint flag = dval; // if dval is 0, then flag is false,otherwise true cval + fval; // cval promoted to int, that intconverted to float sval + cval; // sval and cval promoted to int cval + lval; // cval converted to long ival + ulval; // ival converted to unsigned long usval + ival; // promotion depends on size of unsignedshort and int uival + lval; // conversion depends on size ofunsigned int and long
5. 建议:避免使用强制类型转换 强制类型转换关闭或挂起了正常的类型检查。
强烈建议程序猿避免使用强制类型转换。不依赖强制类型转换也能写出非常好的C++ 程序。
这个建议在怎样看待reinterpret_cast 的使用时很重要。此类强制转换总是很危急的。
相似地,使用const_cast 也总是预示着设计缺陷。设计合理的系统应不须要使用强制转换抛弃const 特性。
其它的强制转换,如static_cast 和dynamic_cast。各有各的用途。但都不应频繁使用。
每次使用强制转换前,程序猿应该细致考虑是否还有其它不同的方法能够达到同一目的。
假设非强制转换不可,则应限制强制转换值的作用域,而且记录全部假定涉及的类型,这样能降低发生错误的机会。
dynamic_cast:dynamic_cast 支持执行时识别指针或引用所指向的对象。
const_cast ,顾名思义,将转换掉表达式的 const 性质。
static_cast: 编译器隐式运行的不论什么类型转换都能够由 static_cast 显式完毕:
double d = 97.0;
// cast specified to indicate that the conversion is intentional
char ch = static_cast<char>(d);
当须要将一个较大的算术类型赋值给较小的类型时,使用强制转换很实用。此时,强制类型转换告诉程序的读者和编译器:我们知道而且不关心潜在的精度损失。对于从一个较大的算术类型到一个较小类型的赋值,编译器一般会产生警告。
当我们显式地提供强制类型转换时。警告信息就会被关闭。
reinterpret_cast:通常为操作数的位模式提供较低层次的又一次解释。
reinterpret_cast 本质上依赖于机器。为了安全地使用 reinterpret_cast。要求程序猿全然理解所涉及的数据类型,以及编译器实现强制类型转换的细节。
6. switch: case 标号必须是整型常量表达式。比如,以下的标号将导致编译时的错误:
// illegal case labelvalues case 3.14: //noninteger case ival: //nonconstant
7. switch 内部的变量定义 :制定这个规则是为避免出现代码跳过变量的定义和初始化的情况。回想变量的作用域。变量从它的定义点開始有效,直到它所在块结束为止。
// Incorrect case true: // error: declaration precedes a case label string file_name = get_file_name(); break; case false: // ... // Correct case true: { // ok: declaration statement within a statement block 277 string file_name = get_file_name(); // ... } break; case false: // ...
8. goto 语句提供了函数内部的无条件跳转,实现从goto 语句跳转到同一函数内某个带标号的语句。
从上世纪60 年代后期開始,不主张使用goto 语句。
goto语句使跟踪程序控制流程变得非常困难,而且使程序难以理解。也难以改动。全部使用goto 的程序都能够改写为不用goto 语句,因此也就没有必要使用goto 语句了。
goto 语句的语法规则例如以下:
goto label;
当中label 是用于标识带标号的语句的标识符。
在不论什么语句前提供一个标识符和冒号。即得带标号的语句:
end: return; //labeled statement, may be target of a goto
形成标号的标识符仅仅能用作goto 的目标。
由于这个原因。标号标识符能够与变量名以及程序里的其它标识符一样,不与别的标识符重名。goto语句和获得所转移的控制权的带标号的语句必须位于于同一个函数内。
goto 语句不能跨越变量的定义语句向前跳转:
// ... goto end; int ix = 10; //error: goto bypasses declaration statement end: // error: code herecould use ix but the goto bypassed its declaration 293 ix = 42; 假设确实须要在goto 和其跳转相应的标号之间定义变量,则定义必须放在一个块语句中: // ... goto end; // ok:jumps to a point where ix is not defined { int ix = 10; // ... code using ix } end: // ix no longervisible here
向后跳过已经运行的变量定义语句则是合法的。为什么?向前跳过未运行的变量定义语句,意味着变量可能在未定义的情况下使用。向后跳回到一个变量定义之前,则会使系统撤销这个变量,然后再又一次创建它:
// backward jump overdeclaration statement ok begin: int sz = get_size(); if (sz <= 0) { goto begin; }
注意:运行goto 语句时,首先撤销变量sz。然后程序的控制流程跳转到带begin: 标号的语句继续运行,再次又一次创建和初始化sz 变量。
9.使用预处理器进行调试
NDEBUG 预处理变量实现有条件的调试代码:
int main() { #ifndef NDEBUG cerr <<"starting main" << endl; #endif // ...
假设NDEBUG 没有定义,那么程序就会将信息写到cerr 中。假设NDEBUG 已经定义了,那么程序运行时将会跳过#ifndef 和#endif 之间的代码。 默认情况下,NDEBUG没有定义,这也就意味着必须运行#ifndef 和#endif 之间的代码。
在开发程序的过程中,仅仅要保持NDEBUG 没有定义就会运行当中的调试语句。
开发完毕后,要将程序交付给客户时,可通过定义NDEBUG 预处理变量,(有效地)删除这些调试语句。大多数的编译器都提供定义NDEBUG 命令行选项: $ CC -DNDEBUG main.C
预处理器还定义了其余四种在调试时很实用的常量:
__FILE__ 文件名称
__LINE__ 当前行号
__TIME__ 文件被编译的时间
__DATE__ 文件被编译的日期
可使用这些常量在错误消息中提供很多其它的信息:
if (word.size() <threshold) cerr <<"Error: " << _ _FILE_ _ << " :line " << _ _LINE_ _ << endl << "Compiled on " << _ _DATE_ _ << " at" << _ _TIME_ _ << endl << " Wordread was " << word << ":Length too short" << endl;
假设给这个程序提供一个比threshold 短的string 对象,则会产生以下的错误信息:
Error: wdebug.cc :line 21
Compiled on Jan 122005 at 19:44:40
Word read was"foo": Length too short
10. 小结
C++ 提供了种类相当有限的语句,当中大多数都会影响程序的控制流:
while、for以及do while 语句,实现重复循环;
if 和switch,提供条件分支结构。
continue。终止当次循环。
break。退出一个循环或switch 语句。
goto,将控制跳转到某个标号语句;