最近又把C语言看了一遍,发现了很多之前学C语言时没有注意到但又很容易出错的地方,现在总结出来和大家一起分享。可能有疏忽纰漏,欢迎大家指正。
一下分为几个部分分别加以说明。
一、关键字
1.什么是定义?什么是声明?两者有何区别?
答:定义是创建一个对象,并未该对象分配一块内存和取一个名字,这个名字就是变量名或者对象名;声明是告诉编译器这个变量或者对象的内存已经存在,这里只是引用。两者最重要的区别在于,定义创建了对象并为对象分配了内存,而声明没有分配内存。
2.static关键字的作用
答:static既可以修饰变量,也可以修饰函数,但两者的作用不一样,下面分别加以说明:
(1)static修饰变量
变量又分为全局变量和局部变量,被static修饰后的变量的内存在静态存储区,准确的说应该是 .data 段(《程序员自我修养》上有详细介绍)
a)静态全局变量
对于一般的全局变量,其生存周期和程序一样,作用域为整个文件,而且如果其他文件想引用该全局变量的话,可以使用extern关键字声明即可。但是当全局变量被static修饰以后,在原来基础上唯一改变的是,其他文件无法再通过extern进行引用。所以一般编程时都会加上static,这样就不用担心自己的变量和其他人定义的变量在命名上发生冲突了。
b)静态局部变量
静态局部变量的作用域和局部变量一样,只局限于某个函数内,其他函数无法引用。但也有其特殊的地方,一般的局部变量在函数调用完成以后会被系统自动释放,但是对于静态局部变量其生存周期和全局变量的周期一样;静态局部变量只初始化一次,下次调用时保持上一次的值而不会重复初始化。
(2)static修饰函数
static修饰函数时,函数的作用域仅局限于本文件(所以又称为内部函数)。使用内部函数的好处是不用担心自己写的函数和其他文件的函数重名。
3.变量命名规则
从我写程序开始,需要定义变量时一直都是看哪个字母闲着就用哪个,常用的有i,j,k......等。程序代码比较少时这样写也没什么能看懂,但是一旦程序代码很多时,再这样给变量命名就会带来很多不必要的麻烦。大家可能和我刚开始一样觉得这是一个很小的问题,但是我发现,养成一个很好的命名习惯不是一下子就能做到的,需要自己在平时养成这样的习惯,下面简单说一下命名规则:
(1)命名时要保证最短的变量名,但要包含最大的信息量;
(2)驼峰式命名,当标识符由多个词组成时,每个词的第一个字母大写,其余全部小写;
(3)标识符分为两部分:前缀_含义。下面列出常用的前缀缩写:
bool--b char--c int--i short--s long--l unsigned--u double--u
pointer--p enum/struct/union--st function_pointer--fp array--*_a typedef enum/struct/union-- *_st
(4)所用的宏定义、枚举常量和const修饰的常量全部大写
4.关键字sizeof
首先要说明的一点是sizeof不是函数,而是一个关键字,是一个运算符。
sizeof计算变量所占空间大小时,括号可以省略;但是就算某种数据类型的内存大小时不能省略。举例如下:
int i= 0;
sizeof(int) sizeof(i) sizeof i //均正确
sizeof int //错误
5.if的条件表达式怎么写
if语句在程序中使用的很是频繁,但大家一般都不会去思考怎么写一个比较合理或者规范的条件表达式,这里说一种常用的规范:
(1)bool变量和零值比较
if(bTestFlag) 或 if(!bTestFlag)
(2)float/double变量和零值比较
float fTestVal = 0.0;
const double EPSINON = 0.000001;
if(fTestVal >= -EPSINON && fTestVal <= EPSINON)
(3)z指针变量和零值比较
if(NULL == p) 或者if(NULL != p)
6.空语句的写法
对于写空语句,如果直接写一个“;”使代码不易阅读。建议写成 NULL; 这种形式。
7.关键字const
(1)const修饰变量
const修饰变量时,变量变为只读变量。但竟然是变量,那就不能作为定义数组时的大小出现,因为数组的大小必须在编译时确定,必须为一个常量。
(2)const修饰指针
const int *p;
int const *p;
int *const p;
const int *const p;
这里提供一种简单的方法来判断const到底修饰的是p还是*p,总结起来就是一句话“忽略类型,净水楼台先得月”。我们判断const的修饰对象时可以忽略类型名,如上面的我们可以忽略int,则变成:
const *p;
const *p;
const p;
const *const p;
8.关键字 volatile
遇到这个关键字声明的变量,编译器对该变量访问时不再进行优化,从而可以提供对特殊地址的稳定访问。
先看看下面的例子:
int i=10;
int j = i;//(1)语句
int k = i;//(2)语句
这时候编译器对代码进行优化,因为在(1)、(2)两条语句中,i 没有被用作左值。这时候编译器认为i 的值没有发生改变,所以在(1)语句时从内存中取出i 的值赋给j 之后,这个值并没有被丢掉,而是在(2)语句时继续用这个值给k 赋值。编译器不会生成出汇编代码重新从内存里取i 的值,这样提高了效率。但要注意:(1)、(2)语句之间i 没有被用作左值才行。
再看另一个例子:
volatile int i=10;
int j = i;//(3)语句
int k = i;//(4)语句
volatile关键字告诉编译器i是随时可能发生变化的,每次使用它的时候必须从内存中取出i的值,因而编译器生成的汇编代码会重新从i 的地址处读取数据放在k 中。
二、符号
1.注释符号
(1)编译器剔除注释时不是简单的去掉,而是用空格来替换
(2)/* */不能嵌套
(3)注释的位置可以放在当前语句行或者上一行,但最好不要放在下一行
2.++/--运算符
++/--在遇到逗号、分号时认为本计算单元已经结束,会进行自加或自减。
三、预处理
1.预处理有三种--文件包含、宏定义和条件编译,这是三种预处理指令,不是C语言的一部分。
2.#运算符
声明一个符号为宏参数,如下:
#define SQR(x) printf("The square of "#x" is %d.\n",((x)*(x))); //被#修饰后,这里的x成为了宏参数
再使用:
SQR(8);
则输出的是:
The square of 8 is 64.
四、指针和数组
1.NULL不要写成Null或者null,影响代码的移植性。
2.如何将数组写入指定地址。
如往内存0x12ff7c地址存入一个整数0x100:
int *p = (int*)0x12ff7c;
*p = 10;
或者:
*(int*)0x12ff7c = 0x100;
3.当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素地址的指针。
4.函数指针、函数指针数组和指向函数指针数组的指针
函数指针:char* (*pfunc)(char *p);
函数指针数组:char* (*pfunc[3])(char *p);
指向函数指针数组的指针:char* (*(*pfunc[3])(char *p);
五、内存管理
1.堆、栈和静态区
静态区:保存自动管全局变量和static变量。静态区的内容在整个程序的声明周期内都存在,由编译器在编译的时候分配。
栈:保存局部变量。栈上的内容只在函数的范围内存在,当函数运行结束,这些内容也会自动销毁。其特点是效率高,但空间大小有限。
堆:由malloc系列函数或new操作符分配的内存。其生命周期由free或delete决定。在没有释放之前一直存在,知道程序结束。其特点是使用灵活,空间比较大,但容易出错。
2. 使用malloc分配内存时,如果分配失败则返回NULL。所以在每次使用malloc函数以后都要使用if(NULL != p)来判断一下是否分配成功。
3. 使用free释放内存后要将指针变量的值置为NULL。