C++回顾——C++中的C

一、创建函数
参数的顺序和类型必须在声明、定义、函数调用中相匹配(必须指明每一个参数的类型)。
在不知道会有多少个参数或什么类型的参数时,可使用可变的参数列表(用…表示)。如果不想使用函数原型的错误检查功能,可以对固定参数表的函数使用可变参数列表。
C++函数原型必须指明函数的返回值类型(在C中,如果省略返回值,默认为整型)。

二、使用C的函数库
在定义自己的函数之前,应该仔细地看一下函数库,可能已经有人解决了我们的问题(且进行了更多的思考和调试)。如果希望程序具有可移植性,就应该限制使用标准库函数。如果必须执行特定平台的活动,应当尽力把代码隔离在某一场所,以便移植到另一平台时容易进行修改。
可以使用库管理器创建自己的函数库。

三、执行控制语句
C++使用C的所有执行控制语句(if-else、while、do-while、for、switch),也允许使用goto语句(避免使用它)。

do-while语句与while语句的区别是,即使表达式第一次计值就等于假,do-while的循环体也至少执行一次;在while语句中,如果条件第一次为假,循环体一次也不执行。

for(initialization;conditional;step){循环体} 表达式中的initialization、conditional、step都可能为空。一旦进入for循环,initialization代码就执行;每一次循环之前,conditional被测试;每一次循环结束时,执行step。
break语句退出循环,不再执行循环中的剩余语句;continue语句停止执行当前的循环,返回到循环的起始处开始新的一轮循环。

switch语句根据一个整型表达式的值从几段代码中选择执行,其形式如下:

switch(selector){
    case integral-value1: statement1;break;
    case integral-value2: statement2;break;
    ...
    default:statement;
}

选择器(selector)是一个产生整数值的表达式。switch语句把选择器的结果和每一个整数值比较。如果发现匹配,则执行对于的语句(statement);如果都不匹配,则执行default语句。case后面的break是可选的,如果省略它,case语句会顺序执行它后面的语句,直到遇到一个break语句。switch语句是一种清晰的实现多路选择的方式,但它需要一个能在编译时求得整数值的选择器(对于其他类型的,则需使用一系列if语句)。

递归要有一种确定“到达底点”递归调用的方法。求解某些具有随意性的复杂问题经常使用递归,因为这时解的具体“大小”不受限制,函数可以一直递归调用,直到问题解决。

四、运算符
任何一种编程语言的加(+)、减(-)、乘(*)、除(/)和赋值(=)的概念都有同样的意义。
运算符优先级规定表达式中出现多个不同运算符时计值的运算顺序。C和C++中有具体的规则来决定计值顺序。如果表达式中运算顺序不清晰,应使用括号使计值次序更加清晰。

自增(++)和自减(–),如果运算符在变量之前出现(如++A),则先执行运算,再产生结果值;如果运算符在变量之后出现(如A++),则产生当前值,再执行运算。
所有的运算符都会从它们的操作数中产生一个值,除了赋值、自增、自减运算符外,运算符所产生的值不会修改操作数。

赋值运算符(=),取右值并把它拷贝给左值(右值可以是任意的常量、变量、表达式,但左值必须是变量)。
整数相除会截取结果的整数部分(不舍入);浮点数不能使用取模运算符(%,从整数相除得到余数)。

关系运算符,在操作数之间建立一种关系(<、>、<=、>=、==、!=)。
逻辑运算符,&&、||、!依据它们的参数的逻辑关系产生true或false。

位运算符,允许在一个数中处理个别的位(因为浮点数使用一种特殊的内部格式,所以位运算符只适用于整型char、int、long)。位运算符(&、|、^、~)对参数中的相应位做布尔代数运算来产生结果。

移位运算符(也是对位的操作),左移位运算符(<<)引起运算符左边的操作数向左移动,右移位运算符(>>)引起运算符左边的操作数向右移动(移动位数都是由运算符后面的操作数指定)。如果移位运算符后面的值比运算符左边的操作数的位数大,则结果是不定的(如果左边的操作数是无符号的,右移是逻辑移位(最高位补0);如果左边的操作数是有符合的,右移可能是逻辑移位,也可能是算术移位)。通常情况下,使用位函数的效率非常高(它们被直接翻译成汇编语言语句)。

三元运算符,如果第一个表达式(后面跟有一个问号)的计值为true,则对紧跟在问号后面的表达式求值,它的结果就是运算符的结果;如果第一个表达式为false,就执行第三个表达式(在冒号后面),它的结果就是运算符的结果。
逗号运算符,1)定义多个变量时用来分隔变量;2)用于函数参数列表;3)用于分隔表达式(它只产生最后一个表达式的值)。

转换运算符,如果编译器明白的话,会自动把一种数据类型转换为另一种类型;强制转换,要用括号把所想要转换的数据类型(包括所有的修饰符)括起来放在值的左边。C++有一个另外的转换语法,它遵从函数调用的语法,这个语法给参数加上括号而不是给数据类型加上括号(如float(200))。

C++的显示转换,应该小心使用,因为转换引入了一个漏洞,并阻止编译器报告在类型方面出错了,程序出故障时首先考虑转换。
静态转换(static_cast)包含的转换类型包括非强制变换、窄化变换、使用void*的强制变换、隐式类型变换、类层次的静态定位。
常量转换(const_cast),从const转换为非const或从volatile转换为非volatile(这是const_cast唯一允许的转换)。
重解释转换(reinterpret_cast,最不安全的一种转换机制 ),转换为完全不同的意思,为了安全使用它,必须转换回原来的类型。转换成的类型一般只能用于位操作,否则就是为了其他隐秘的目的。使用reinterpret_cast通常是一种不明智、不方便的编程方式,但是当必须使用它时,它是非常有用的。
向下类型转换(dynamic_cast),使用它试着向下类型转换一个特定的类型,仅当类型转换是正确的并且是成功的时,返回值会是一个指向所需类型的指针,否则它将返回0来表示这并不是正确的类型。当使用dynamic_cast时,必须对一个真正多态的层次进行操作(含有虚函数),因为dynamic_cast使用了存储在VTABLE中的信息来判断实际的类型。在进行向下类型转换时,要检验以确保类型转换的返回值为非0(但不能确保指针完全一样,因为通常在向上类型转换和向下类型转换时指针会进行调整,特别是在多重继承的情况下)。dynamic_cast运行时需要一点额外的开销,如果执行大量的dynamic_cast就会影响性能。如果我们在进行向下类型转换时知道正在处理的是何种类型,可以使用static_cast来代替dynamic_cast(static_cast不允许类型转换到该类层次的外面,所以会更安全,但是,静态地浏览类层次总是有风险的,所以除非特殊情况,一般还是使用dynamic_cast)。
独立运算符(sizeof),提供对有关数据项目所分配的内存大小。如果把它应用于一个类型,必须要使用括号,如果对一个变量使用它,可以不使用括号。
asm关键字,允许在C++程序中写汇编代码。在汇编程序中可以引用C++变量,这意味着可以方便地和C++代码通信,且限定汇编代码只是用于必要的高效调整,或使用特殊的处理器指令。编写汇编语言时所必须使用的严格语法是依赖于编译器的。
显示运算符,是用于位运算和逻辑运算符的关键字。and(&&)、or(||)、not(!)、not_eq(!=)、bitand(&)、and_eq(&=)、bitor(|)、or_eq(|=)、xor(^)、xor_eq(^=)、compl(~)。

五、数据类型
在编程时,数据类型定义使用存储空间(内存)的方式,告诉编译器怎样创建一片特定的存储空间,以及怎样操纵这片存储空间。数据类型可以是内部或抽象的。内部数据类型是编译器本来能理解的数据类型(C和C++的内部数据类型几乎是一样的);抽象数据类型是程序员创建的类,编译器通过读包含类声明的头文件认识怎样处理抽象数据类型。

标准的内部类型规范不说明每一个内部类型必须有多少位,规范只规定内部类型必须能存储的最大值和最小值。C和C++中有4个基本的内部数据类型(这里描述的是基于二进制的机器),char用于存储字符,使用最小的8位(一个字节)存储,尽管它可能占用更大的空间;int存储整数值,在之前16位微型机中使用最小两个字节的存储空间(目前使用4个字节的存储空间)。float(单精度浮点数)和double(双精度浮点数)类型存储浮点数,一般使用IEEE的浮点格式。
标准C++的bool类型有两种由内部的常量true(转为整数是1)和false(转为整数是0)表示。此外,一些语言元素产生类似bool类型的行为,也已被采纳,如带布尔参数并产生bool结果的(&&、||、!、<>、<=、>=、==、!=)、条件表达式转为bool值的(if、for、while、do-while)、第一个操作数转为bool值的(?:)、指针在必要的时候也自动转为bool值、int转为bool(非零值为true而零值为false)等。

说明符(specifier)用于改变基本内部类型的含义并把它们扩展成一个更大的集合,有4个说明符:long、short、signed、unsigned。
long和short修改数据类型具有的最大值和最小值。一般的int必须至少有short int型的大小。整数类型的大小等级是:short int、int、long int。浮点数的大小等级是:float、double、long double。
signed和unsigned修饰符告诉编译器怎样使用整数类型和字符的符号位。
C和C++中的‘&’告诉我们元素的地址(相继定义的变量在内存中是连续存放的,它们根据各自的数据类型所要求的字节数分隔开)。C和C++中有一个专门存放地址的变量类型,叫指针(pointer)(可以保存地址,可以使用地址去修改原先的变量)。
通常,向参数传递参数时,有按值传递(在函数内部生成该参数的一个拷贝)和按地址传递(允许函数去改变外部对象)。

六、作用域
作用域规则指明变量的有效范围,它在哪里创建,在哪里销毁。变量的有效作用域从它的定义点开始,到和定义变量之前最邻近的开括号配对的第一个闭括号(作用域由变量所在的最近一对括号确定)。作用域可以嵌套。定义变量时,C强制在作用域的开始处就定义所有的变量,以便在编译器创建一个快时,能给所有这些变量分配空间;C++允许在作用域的任意地方定义变量(可以在正好使用它之前定义)。

七、指定存储空间分配
全局变量是在所有函数体的外部定义的,程序的所有部分都可以使用(全局变量的生命期一直到程序的结束;如果在一个文件中使用定义在另一个文件中的全局变量,则用extern来声明)。

局部变量出现在一个作用域内,它们局限于一个函数。局部变量经常被称为自动变量(auto可以显示地说明这个问题),因为它们在进入作用域时自动生成,离开作用域时自动消失。寄存器变量是一种局部变量,关键字register告诉编译器“尽可能快地访问这个变量”。加速访问速度取决于实现,正如名字所暗示的那样,这通常是通过在寄存器中放置变量来做到的。因为并不能保证将变量放置在寄存器中,也不能保证提高访问速度,所以这只是对编译器的一个暗示。使用register变量是有限制的,不可能得到或计算register变量的地址,register变量只能在一个块中声明(无全局的或静态的register变量),可以在函数的参数列表中使用register变量。因为编译器的优化器可能比我们做得更好,故避免使用关键字register。

静态变量,如果想使局部变量的值在程序的整个生命期里仍然存在,可以定义函数的局部变量为static,并给它一个初始值(静态局部变量,其优点是在函数范围之外它是不可用的,这会使错误局部化)。当应用static于函数名和全局变量时,它的意思是“在文件的外部不可使用这个名字”(函数名或变量是局部于文件的,具有文件作用域)。

八、连接
在一个执行程序中,标识符代表存放变量或被编译过的函数体的存储空间。连接用连接器所见的方式描述存储空间。连接方式有两种:内部连接(internal linkage)和外部连接(external linkage)。
内部连接,只对正被编译的文件创建存储空间。用内部连接,别的文件可以使用相同的标识符或全局变量,连接器不会发现冲突(也就是为每一个标识符创建单独的存储空间)。在C/C++中,内部连接使用static指定。
外部连接,为所有被编译过的文件创建一片单独的存储空间。一旦创建存储空间,连接器必须解决所有对这片存储空间的引用。全局变量和函数名有外部连接(使用extern声明)。函数之外定义的所有变量(C++中除了const)和函数定义默认为外部连接(可以使用static强制它们具有内部连接,也可使用extern显示指定标识符具有外部连接,在C++中对于const有时必须使用extern)。
调用函数时,局部变量(自动变量)只是临时存在于堆栈中,连接器不知道它们,所以这些变量没有连接。

九、常量
const由C++采用,并加进标准C中。在C中,编译器对待const变量如同其他变量一样,只不过带有一个特殊的标记(如果在不同的文件中或头文件中定义多个同名的const变量,连接器将生成发生冲突的错误消息)。
在C++中,一个const变量必须有初始值(在C中不要求)。常量值前带0被认为是八进制(基数为8),带0x被认为是十六进制(基数为16),如果没有修饰,编译器会认为常量值是十进制(基数为10)。浮点数可以含有小数点和指数幂(用e表示,“10的幂”),可以对数加后缀强加浮点数类型:f或F强加float型,l或L强加long double型,否则是double型。
字符常量是用单引号括起来的字符(如‘A’),也可用八进制或十六进制表示字符常量。

十、volatile变量
const告诉编译器“这是不会改变的”(运行编译器执行额外的优化);volatile则告诉编译器“不知道何时会改变”(防止编译器依据变量的稳定性做任何优化)。当编译器不进行优化时,volatile可能不起作用,但是当开始优化代码时(当编译器开始寻找冗余的读入时),可以防止出现重大的错误。

十一、创建复合类型
1、用typedef命名别名
typedef 原类型名 别名
(我们可能认为使用预处理程序置换就可以很容易实现,但在一些重要场合,编译器必须知道我们正在将名字当做类型处理,所以typedef起了关键作用。)

2、用struct把变量结合在一起
struct是把一组变量组合成一个构造的一种方式。struct的声明必须以分号结束。
C++ class对象是由struct演化而来的,所以struct是语法的来源。

3、用enum提高程序清晰度
枚举数据类型是把名字和数字相联系的一种方式,增强了代码的可读性,其声明和struct的声明相似。如果对某些名字赋值,对其他的不赋值,编译器会使用相邻的下一个整数值。
在C++中对枚举的类型检查比在C中更为严格,任何时候写代码对enum类型进行隐式转换,编译器都会标记这是一个危险活动。

4、使用union节省内存
union把所有的数据放进一个单独的空间内,它计算出放在union中的最大项所必需的空间数,并生成union的大小。每当在union中放置一个值,这个值总是放在union开始的同一个地方,但是只使用必需的空间(所有的union变量地址都是一样的,在类或struct中,变量地址是不同的)。在union中声明某个数据类型的多个实例是没有意义的(除非就是要用不同的名字)。

5、数组
数组是一种复合类型,因为它们允许在一个单一的标识符下把变量结合在一起,一个接着一个。数组名是数组的起始地址。数组只能作为指针传递。

十二、调试建议
1、预处理器调试标记
通过使用预处理器#define定义一个或多个调试标记,可以测试一个使用#ifdef语句和包含条件调试代码的标记。当认为调试完成了,只需使用#undef标记,代码就会自动消失。
为了区分预处理器标记和变量,预处理器标记一般用大写字母书写。

2、运行期调试标记
某些情况下,在程序执行期间打开和关闭调试标记会更加方便,特别是使用命令行在启动程序时设置它们。

3、把变量和表达式转换成字符串
写调试代码时,可以使用标准C的字符串化运算符’#’(在一个预处理器宏中的参数前面使用一个#,预处理器会把这个参数转换为一个字符数组),生成一个宏用于调试期间打印出变量的值。

4、C语言assert()宏
当使用assert()时,给它一个参数,即一个表示断言为真的表达式。预处理器产生测试该断言的代码。如果断言不为真,则在发出一个错误信息告诉断言是什么以及它失败之后,程序会终止。当完成调试后,通过在程序的#include之前插入语句行#define NDEBUG或在编译器命令行中定义ndebug,可以消除宏产生的代码。

十三、函数地址
一旦函数被编译并载入计算机中执行,它就会占用一块内存(这块内存有一个地址,因此函数也有地址)。
1、定义函数指针
要定义一个指针指向一个无参无返回值的函数,可以写成:
void (*funcPtr)();
当看到类似复杂定义时,最好的处理方法是从中间开始(从变量名开始)和向外扩展(先注意右边最近的项,然后注意左边(用星号表示的指针),再注意右边(参数列表),在注意左边(返回值))。

2、指向函数的指针数组
指向函数的指针数组,为了选择一个函数,只需使用数组的下表,然后间接引用这个指针。这种方式支持表格式驱动码(可以根据状态变量去选择被执行函数),这种设计对于经常要从表中添加或删除函数(动态创建或改变表)十分有用。

十四、make:管理分段编译
当使用分段编译时,需要某种方法自动编译每个文件并且告诉连接器把所有分散的代码段,连同适当的库和启动代码,构成一个可执行的文件。
make工具按照一个名为makefile的文本文件(这个文件列出了源代码文件间的依赖关系)中的指令去管理一个工程中的所有单个文件。当编辑了工程中的某些文件并使用make时,make程序会按照makefile中的说明去比较源代码文件与相应目标文件的日期,如果源代码文件的日期比它的目标文件的日期新,make会调用编译器对源代码进行编译(make仅仅编译已经改变了的源代码,以及其他受修改文件影响的源代码文件)。
在makefile中的所有注释都从“#”开始一直延续到本行的末尾。makefile可以包含某些宏(等号‘=’用来把字符串定义为一个宏,符合‘$’和圆括号扩展宏)。
后缀规则:一条后缀规则是一种教make怎样从一类型文件(如.cpp)转化为另一种类型(如.obj)的方法。后缀规则告诉make可以根据文件的扩展名去考虑怎样构建程序而不需要显示规则去构建一切。

猜你喜欢

转载自blog.csdn.net/zlanbl085321/article/details/80800767