1.命令行编译
根据编译器的不同,会有不同的选项,在类Unix下利用gcc或g++编译,例如
$ g++ prog.cpp –o prog
运行程序
$ ./prog
获得命令状态
$ echo $?
Windows下微软编译器命令,例如
C:\directory> cl –GX prog.cpp
执行程序
C:\directory> a.exe
C:\directory> echo %ERRORLEVEL%
文件结束符和换行符
在Windows中CTRL+z在命令行输入文件结束符,在类Unix中CTRL+d输入文件结束符;Windows中换行符是\r\n,在类Unix中为\n。
更多的编译选项参考相关编译器使用手册。
2.基本IO流对象
cin 标准输入, 有缓冲
cout 标准输出, 有缓冲
cerr 标准错误,用来输出警告和错误信息, 无缓冲
clog 日志流绑定到标准错误的ostream对象,用来记录程序产生的一般性信息到日志文件, 有缓冲
在类Unix系统中cin、cout、cerr分别具有文件描述符0、1、2。
一般情况下,IO设备将输入输出数据存储到缓冲区中,并独立于程序动作来对缓冲区进行读写。输出缓冲区通常必须显式刷新,以强制输出缓冲区内容。
默认情况下,读cin会刷新cout;程序正常结束时,cout也被刷新。
3.C++基本内置类型
C的基本类型有:char, short, int, long, float, double, long double
在C类型的基础上增加了bool, wchar_t
bool只有两种值true和false;wchar_t字面值字符和字符串举例: L'a'; L”ABcd”
4.C++的作用域有6种:
全局作用域,可用作用域操作符::name引用全局作用域中的标识符
命名空间作用域,可用namesapce::name引用
类作用域,可用MyClass::name引用
函数作用域,也是局部作用域
块作用域,在{int name; ….}之间
语句作用域,名字在语句的条件内定义,例如do {} while (int name = 0);
5.变量一定要在使用之前初始化,否则其值是不确定的
内置类型变量在函数体外都默认初始化为0值,在函数体内不进行初始化,此时程序员要自己进行初始化,否则可能会有不确定的结果。未初始化变量引起的错误难以发现,永远不要依赖未定义的行为。建议对每个内置类型对象都要初始化。
类类型变量不管在哪定义,如果该类具有默认构造函数,则当未提供初始化式时,就会使用默认构造函数进行初始化。 对于没有默认构造函数的类就必须提供显式的初始化式,没有初始值是根本不可能定义这种类的对象。
6.编程风格一定要一致
1) 例如变量命名:firstBook, 函数命名createFile, 类名class MyStream{ … };
2) {和}我喜欢放在独立的一行,并且在同一列上
例如:
for (int j = 0; j < length; ++j)
{
…
}
3) if语句块不管有多少必须用{和}括起来
if (buffSize < SIZE)
{
// only one statement
buffSize += extendedSize;
}
4) 空格的使用是有讲究的
在关键字之后需要一个空格,例如while (condition),而不是while(condition)。
二元操作符的前后都需要留一个空格,例如a = a + b; b = a – b; a = a – b;来交换数据。
在函数定义是不要在函数名和左括号之间加空格,应该void createCat(param1, param2, …)。
5) switch中case的break的使用
在不要break的case中一定要加注释说明是故意省去break的;而不是粗心大意,例如 switch (position)
{
case HEAD:
++i;
++j;
// 省去break,为了方便使用...
case MEDIUM:
position = curPos;
break;
case END:
position = prePos;
break;
default:
printError("Invalid position!");
break;
}
6)注释的风格
采取当行注释风格,在注释之前有空行
在开始编写每个类和函数时都要有一段注释来说明函数的功能,作者,history,revision,各种参数的作用;在以后维护此函数或类时,在增加、修改和删除之前都要有注释说明为什么,修改日期,作者等信息,这方便以后的程序维护工作。
7) 风格有待补充
7.头文件中只提供编译器必须需要的内容
头文件用于声明而不是定义。头文件一般包括类的定义、extern变量声明、函数声明、值在编译时就知道的const对象和inline函数。例外的三种:类的定义、 值在编译时就知道的const对象和inline函数是定义而不是声明,这三种定义可以在多个源文件中定义,只要每个源文件中的定义相同。
在头文件中允许这三个定义,因为编译器编译时需要知道他们的定义。
const默认是局部于它所在的文件,这样就允许const对象定义在头文件中,每个引用此头文件的源文件都包含自己的const对象。实践中编译器将所有引用常量表达式初始化的const对象全部替换为相应的常量表达式,不会对局部于每个源文件中const对象的定义都关联一块内存。这相当于宏,而宏是在预处理时扩展。
8.const的使用
const表示它所修饰的常量不可改变
const int size = 10; // size为常量,编译时常量表达式
const int len = getLength(); // 运行时才得到值
引用,普通的non-const引用只能绑定到与该引用同类型的对象;const引用可以绑定到不同但相关类型对象或绑定到右值。
int number = 10;
const int SIZE = 1;
int &ref1 = number;
int &ref2 = SIZE; // error
const int &cRef1 = number;
const int &cRef2 = SIZE;
const int &cRef3 = 100;
指针,同样,non-const指针只能指向同类型的对象;const指针则可能会指向non-const对象。区分指针本身为const和声明它所指对象为const的不同。
可以修改const指针所指向的对象。因为const指针中的const只是说明这个指针可能指向const对象,至于对象能否修改完全取决于该对象的类型。
区分四种情况:
int colorR = 255;
int *pi = &colorR; // pi为non-const,本身可以修改指向别的对象,可以通过pi对所指对象修改
const int *pi = &colorR; // pi为non-const, 本身可以修改指向别的对象,不可以通过此指针修改它指向的对象
int * const pi = & colorR; // pi 为const,本身不可以修改,必须初始化,可以通过pi对所指对象修改
const int * const pi = &colorR; // pi本身const,不可修改,也不可以通过它修改所指对象
注意: 对象colorR可以通过其他方式修改,因为它是non-const的。
std::string s = "demo";
typedef std::string *pstring;
const pstring cstr = &s;
cstr的类型是string * const的,这里const修饰pstring类型,cstr是pstring类型的const对象,与string * const cstr;等价,说明cstr为const,本身不可修改,所指对象可以通过它来修改。
const pstring pstr; // const修饰pstring,说明pstr是const,本身不可修改
const string *pstr1; // const修饰string,说明不可以通过pstr1来修改指向的对象
string * const pstr2; // const修饰pstr2,说明pstr2是const,本身不可修改
9.enum的使用
定义相关联的常量值
enum Points
{
point1,
point2,
point3 = 1,
point4 = 3,
point5
};
point1 = 0, point2 = 1, point5 = 4;
10.extern的使用
extern表示声明变量名而不定义它。不定义变量的声明包括对象名、对象类型和关键字 extern。
例如:extern double salary = 10000; // ok: definition
extern double salary; // ok: declaration not definition
double salary; // error: redefinition
在多个文件中使用的变量,需要有与定义分离的声明,此时一个文件含有定义,在相应 的头文件中包含声明,在需要使用此变量的文件中包含有声明的头文件。例如
// Extern.h
#ifndef _EXTERN_H
#define _EXTERN_H 1
#define TEXT(x) # x
#include
// 多个文件中共享变量和常量
extern long curTime;
// error: 在多个文件中引用时,在链接阶段会出现重复定义错误
//extern const int STATUS = 1;
// ok: 在其中一个文件中定义,其他文件中引用这个头文件,这跟普通共享变量一样的使用方式
//extern const int STATUS;
// ok: 在头文件中定义常量,这种方式是推荐的
const int STATUS = 1;
extern void printMessage();
#endif
// Extern.cpp
#include "Extern.h"
long curTime = 0;
void printMessage()
{
std::cout <<__FILE__ << "/" << __func__ << ":" << std::endl;
std::cout << "\t" << TEXT(STATUS) << " = " << STATUS << std::endl;
std::cout << "\t" << TEXT(curTime) << " = " << curTime << std::endl;
}
// Main.cpp
#include "Extern.h"
const int STATUS = 2;
int main(int argc, char* argv[])
{
printMessage();
curTime += 100;
std::cout <<__FILE__ << "/" << __func__ << ":" << std::endl;
std::cout << "\t" << TEXT(STATUS) << " = " << STATUS << std::endl;
std::cout << "\t" << TEXT(curTime) << " = " << curTime << std::endl;
printMessage();
}
11.预处理器指令
1) #、##
#将它后面的第一个参数用引号括起来作为字符串返回
#define to_string(s) # s
cout << to_string(hello world!) << endl;
在预处理之后会变成:
cout << "hello world!" << endl;
##将其前后参数连接起来返回
#define concatenate( x, y ) x ## y
int xy = 10;
cout << concatenate( x, y ) << endl;
在预处理之后会变成:
cout << xy<< endl; // display '10' to standard output.
2) #define
语法:#define macro-name replacement-string
宏替换,定义宏时需要注意使用额外的左右括号(、),来避免一些在替换后异想不到的结果。
#define absolute_value( x ) ( ((x) < 0) ? -(x) : (x) )
...
int num = -1;
while( absolute_value( num ) )
{
...
}
复杂宏中每个变量都用括号括起来,使它作为一个整体来计算。整个宏也括起来,避免扩展后被其他code干扰或污染。
3) #error message
当遇到这个指令,编译器停止编译并且吐出当前行号和message,这条指令对于调试很有用。
4) #if, #ifdef, #ifndef, #else, #elif, #endif
条件预处理指令,在某种情况下包含或不包含某些code。
5) #include 搜索从标准库目录开始
#include "filename"搜索从当前目录开始
6) #line line_number ["filename"]
改变LINE和FILE变量,LINE和FILE分别标示当前处理的行和文件名
#line 10 "main.cpp"
改变当前行为10,当前文件名为main.cpp
7) #undef variable去掉前面定义的宏变量
8) __LINE__, __FILE__, __DATE__, __TIME__, __cplusplus, __STDC__
__LINE__ and __FILE__: current line and current file being processed.
__DATE__ : contains the current date, in the form month/day/year. This is the date that the file was compiled, not necessarily the current date.
__TIME__: represents the current time, in the form hour:minute:second. This is the time that the file was compiled, not necessarily the current time.
The __cplusplus variable is only defined when compiling a C++ program.
The __STDC__ variable is defined when compiling a C program, and may also be defined when compiling C++.
GCC-specific variables
__func__ contains the bare name of the function, this is part of the C99 standard. 不加任何修饰的函数名,也就是unadorned name.
__FUNCTION__ is another name for __func__, not standardized.
The __PRETTY_FUNCTION__ contains the type signature of the function as well as its bare name. pretty function means having modifiers.
9) 最复杂的预处理指令#pragma,来自网络博客
http://hi.baidu.com/sihochina/blog/item/832abb2614e128128a82a1c3.html
在所有的预处理指令中,#pragma指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个 编译器给出了一个方法,在保持与C和C ++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。
其格式一般为: #pragma Para
其中Para 为参数,下面来看一些常用的参数。
(1)message 参数。
message 参数是我最喜欢的一个参数,它能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:
#pragma message(“消息文本”)
当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的 设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法
#ifdef _X86
#pragma message(“_X86 macro activated!”)
#endif
当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_X86 macro activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了。
(2) 另一个使用得比较多的pragma参数是code_seg。格式如:
#pragma code_seg( [\section-name\[,\section-class\] ] )
它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。
(3) #pragma once (比较常用)
只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。
(4) #pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。BCB可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太 多磁盘空间,所以使用这个选项排除一些头文件。
有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragmastartup指定编译优先级,如果使用 了#pragma package(smart_init) ,BCB就会根据优先级的大小先后编译。
(5) #pragma resource \*.dfm\表示把*.dfm文件中的资源加入工程。*.dfm中包括窗体外观的定义。
(6) #pragma warning( disable : 4507 34; once : 4385; error : 164 )
等价于:
#pragma warning(disable:4507 34) // 不显示4507和34号警告信息
#pragma warning(once:4385) // 4385号警告信息仅报告一次
#pragma warning(error:164) // 把164号警告信息作为一个错误。
同时这个pragma warning 也支持如下格式:
#pragma warning( push [ ,n ] )
#pragma warning( pop )
这里n代表一个警告等级(1---4)。
#pragma warning( push )保存所有警告信息的现有的警告状态。
#pragma warning( push, n)保存所有警告信息的现有的警告状态,并且把全局警告等级设定为n。
#pragma warning( pop )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的一切改动取消。例如:
#pragma warning( push )
#pragma warning( disable : 4705 )
#pragma warning( disable : 4706 )
#pragma warning( disable : 4707 )
//.......
#pragma warning( pop )
在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)。
(7) pragma comment(...)
该指令将一个注释记录放入一个对象文件或可执行文件中。常用的lib关键字,可以帮我们连入一个库文件。
(8) 通过#pragma pack(n)改变C编译器的字节对齐方式
在c语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、 结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然对界(alignment)条件分配空间。各个成员按照它们被声明的顺序在内存中顺 序存储,第一个成员的地址和整个结构的地址相同。
例如,下面的结构各成员空间分配情况:
struct test
{
char x1;
short x2;
float x3;
char x4;
};
结构的第一个成员x1,其偏移地址为0,占据了第1个字节。第二个成员x2为short类型,其起始地址必须2字节对界,因此,编译器在x2和x1之间填 充了一个空字节。结构的第三个成员x3和第四个成员x4恰好落在其自然对界地址上,在它们前面不需要额外的填充字节。在test结构中,成员x3要求4字 节对界,是该结构所有成员中要求的最大对界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空间为 12字节。更改C编译器的缺省字节对齐方式。在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:
使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
使用伪指令#pragma pack (),取消自定义字节对齐方式。
另外,还有如下的一种方式:
__attribute((aligned(n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
__attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
以上的n = 1, 2, 4, 8, 16... 第一种方式较为常见。
应用实例
在网络协议编程中,经常会处理不同协议的数据报文。一种方法是通过指针偏移的方法来得到各种信息,但这样做不仅编程复杂,而且一旦协议有变化,程序修 改起来也比较麻烦。在了解了编译器对结构空间的分配原则之后,我们完全可以利用这一特性定义自己的协议结构,通过访问结构的成员来获取各种信息。这样做, 不仅简化了编程,而且即使协议发生变化,我们也只需修改协议结构的定义即可,
其它程序无需修改,省时省力。下面以TCP协议首部为例,说明如何定义协议结构。
其协议结构定义如下:
#pragma pack(1) // 按照1字节方式进行对齐
struct TCPHeader
{
short SrcPort; // 16位源端口号
short DstPort; // 16位目的端口号
int SerialNo; // 32位序列号
int AckNo; // 32位确认号
unsigned char HaderLen : 4; // 4位首部长度
unsigned char Reserved1 : 4; // 保留6位中的4位
unsigned char Reserved2 : 2; // 保留6位中的2位
unsigned char URG : 1;
unsigned char ACK : 1;
unsigned char PSH : 1;
unsigned char RST : 1;
unsigned char SYN : 1;
unsigned char FIN : 1;
short WindowSize; // 16位窗口大小
short TcpChkSum; // 16位TCP检验和
short UrgentPointer; // 16位紧急指针
};
#pragma pack() // 取消1字节对齐方式
当使用#pragma pack(n)伪指令时,对齐规则如下:
1、数据成员对齐规则:结构(struct)(或联
合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma
pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2、结构(或联合)的整体对齐规则:在数据成员完成
各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
结合1、2推断:当#pragma
pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
测试:
#include
#include
#pragma pack(2)
typedef struct FirstBlock_Tag
{
char filed1;
long filed2;
short filed3;
double filed4;
int filed5;
} FirstBlock;
#pragma pack()
#pragma pack(4)
typedef struct SecondBlock_Tag
{
char filed1;
long filed2;
short filed3;
double filed4;
FirstBlock filed5;
} SecondBlock;
#pragma pack()
#pragma pack(8)
typedef struct ThirdBlock_Tag
{
char filed1;
long filed2;
short filed3;
double filed4; // 4字节对齐
int filed5;
char filed6;
} ThirdBlock;
#pragma pack()
//#pragma pack(2)
// default alignment, 4字节
typedef struct FourthBlock_Tag
{
char filed1;
int filed2;
short filed3;
char filed4;
ThirdBlock filed5;
} FourthBlock;
//#pragma pack()
int main(int argc, char* argv[])
{
printf(__STRING(FirstBlock)" sizeof and offset info: \n");
printf(__STRING(sizeof(FirstBlock))": %d\n", sizeof(FirstBlock));
printf(__STRING(offestof(FirstBlock, filed1))": %d\n", offsetof(FirstBlock, filed1));
printf(__STRING(offsetof(FirstBlock, filed2))": %d\n", offsetof(FirstBlock, filed2));
printf(__STRING(offsetof(FirstBlock, filed3))": %d\n", offsetof(FirstBlock, filed3));
printf(__STRING(offsetof(FirstBlock, filed4))": %d\n", offsetof(FirstBlock, filed4));
printf(__STRING(offsetof(FirstBlock, filed5))": %d\n", offsetof(FirstBlock, filed5));
printf("\n\n");
printf(__STRING(SecondBlock)" sizeof and offset info: \n");
printf(__STRING(sizeof(SecondBlock))": %d\n", sizeof(SecondBlock));
printf(__STRING(offsetof(SecondBlock, filed1))": %d\n", offsetof(SecondBlock, filed1));
printf(__STRING(offsetof(SecondBlock, filed2))": %d\n", offsetof(SecondBlock, filed2));
printf(__STRING(offsetof(SecondBlock, filed3))": %d\n", offsetof(SecondBlock, filed3));
printf(__STRING(offsetof(SecondBlock, filed4))": %d\n", offsetof(SecondBlock, filed4));
printf(__STRING(offsetof(SecondBlock, filed5))": %d\n", offsetof(SecondBlock, filed5));
printf("\n\n");
printf(__STRING(ThirdBlock)" sizeof and offset info: \n");
printf(__STRING(sizeof(ThirdBlock))": %d\n", sizeof(ThirdBlock));
printf(__STRING(offsetof(ThirdBlock, filed1))": %d\n", offsetof(ThirdBlock, filed1));
printf(__STRING(offsetof(ThirdBlock, filed2))": %d\n", offsetof(ThirdBlock, filed2));
printf(__STRING(offsetof(ThirdBlock, filed3))": %d\n", offsetof(ThirdBlock, filed3));
printf(__STRING(offsetof(ThirdBlock, filed4))": %d\n", offsetof(ThirdBlock, filed4));
printf(__STRING(offsetof(ThirdBlock, filed5))": %d\n", offsetof(ThirdBlock, filed5));
printf(__STRING(offsetof(ThirdBlock, filed6))": %d\n", offsetof(ThirdBlock, filed6));
printf("\n\n");
printf(__STRING(FourthBlock)" sizeof and offset info: \n");
printf(__STRING(sizeof(FourthBlock))": %d\n", sizeof(FourthBlock));
printf(__STRING(offsetof(FourthBlock, filed1))": %d\n", offsetof(FourthBlock, filed1));
printf(__STRING(offsetof(FourthBlock, filed2))": %d\n", offsetof(FourthBlock, filed2));
printf(__STRING(offsetof(FourthBlock, filed3))": %d\n", offsetof(FourthBlock, filed3));
printf(__STRING(offsetof(FourthBlock, filed4))": %d\n", offsetof(FourthBlock, filed4));
printf(__STRING(offsetof(FourthBlock, filed5))": %d\n", offsetof(FourthBlock, filed5));
}
结果如下:
// FirstBlock为2字节对齐FirstBlock sizeof and offset info:
sizeof(FirstBlock): 20
offestof(FirstBlock, filed1): 0
offsetof(FirstBlock, filed2): 2
offsetof(FirstBlock, filed3): 6
offsetof(FirstBlock, filed4): 8
offsetof(FirstBlock, filed5): 16
// SecondBlock为4字节对齐SecondBlock sizeof and offset info:
sizeof(SecondBlock): 40
offsetof(SecondBlock, filed1): 0
offsetof(SecondBlock, filed2): 4
offsetof(SecondBlock, filed3): 8
offsetof(SecondBlock, filed4): 12
offsetof(SecondBlock, filed5): 20
// ThirdBlock为4字节对齐
ThirdBlock sizeof and offset info:
sizeof(ThirdBlock): 28
offsetof(ThirdBlock, filed1): 0
offsetof(ThirdBlock, filed2): 4
offsetof(ThirdBlock, filed3): 8
offsetof(ThirdBlock, filed4): 12
offsetof(ThirdBlock, filed5): 20
offsetof(ThirdBlock, filed6): 24
// FourthBlock为4字节对齐FourthBlock sizeof and offset info:
sizeof(FourthBlock): 40
offsetof(FourthBlock, filed1): 0
offsetof(FourthBlock, filed2): 4
offsetof(FourthBlock, filed3): 8
offsetof(FourthBlock, filed4): 10
offsetof(FourthBlock, filed5): 12
阅读(961) | 评论(0) | 转发(0) |