读书笔记——C语言深度剖析

一、关键字
变量(遵循变量命名规则)

auto(声明自动变量,缺省时编译器默认):定义域与生存周期均在语句块内、
static(声明静态变量):类似全局变量,区别于仅属于拥有它的函数,作用域为定义处至文件结尾,使用范围为定义的函数体内,则可用于记录某函数被调用次数,i=10 j=1、
const(声明只读变量):定义同时需初始化,编译时确定其值、

     const int M = 10;
     int const M = 10int i = M; //此时才为M分配内存
     const int *p ; //指向的对象不可变

struct(声明结构体变量): 空结构体所占空间1byte,默认public,类似class、
extern(引用变量):即外来的变量、

     const int n = 10;// file1.h
     #include” file1.h”
     extern int n = 10;// file2.h//n为共享存储区域
     //c++中,
     extern int add(int i, int j);
     extern “C”{ #include” file1.h”}
     //c中,
     extern int add(int i, int j),;

基本数据类型、

register(声明寄存器变量,提高效率):单个值,长度小于等于整型长度,将变量存在CPU内部寄存器中避免内存地址寻访尽可能提高效率,故不能用&获取该变量地址,若需获取该变量地址?
voilatile(声明为可隐含改变的变量):易变不稳定,重新访问变量地址;const

1、类型
union(声明联合数据类型)、
enum(声明枚举类型)、
signed/unsigned(声明有/无符号类型):首位为符号位,值为除最高位以外剩余的,缺省默认signed,补码存储,计算需谨慎 0;10 隐式转化;无限循环,unsigned i,i>=0恒成立
typedef(给数据类型取别名)

2、语句
if -else: 先处理正常情况,异常情况最后处理
switch与case: case后跟整型、字符型常量、整型表达式(最简),排列顺序由正常至异常,由高频至低频

      break;//终止选择
      default;//仅用于检查真正的默认情况

while循环:whlie( i ) { } // i != 0
do-while循环:do{ }while( i ); // i == 0
for循环:“半开半闭写法”,易控制循环次数,多用于事先知道循环次数的情况
注意: 多重循环中,最长循环置于最内层,依次向外;
不能在循环体内修改循环变量

4、void(声明空类型指针、声明函数无返回值或实参):
空类型可包容有类型,其余需遵循转化规则,不代表一个真实的变量;
函数无参或无返回值时时需明确指明,避免歧义,int fun(void);
空类型指针慎用

5、return(子程序返回语句):

return(); // 终止函数并返回其后所跟值
          //不可返回指向“栈内存”的“指针”(数组名)

二、符号
1、/* */与//:注释
空格代替注释;
函数出入口、全局数据加注释;
避免二义性,原因,简洁
2、\:转义 接续
转义字符的开始标志;
表示换行(编译器会将其剔除),后应紧跟字符
3、’字符常量’
“字符串常量”
4、
&&逻辑与:一假则假(遇假停止) || 逻辑或:一真则真(遇真停止)–>短路问题

int a =1;
int b =0;
int c =2; 
if(a++ && b++ && c++)//b = 0为假
{printf("true\n");} 
else
{printf("false\n");}
printf("%d,%d,%d"a,b,c); //2,1,0   
if(a++||b++||c++) //a=0为真 
{printf("true\n");} 
else 
{printf("false\n");}
printf("%d,%d,%d"a,b,c); //2,0,2

5、位运算符:
11:0000 1011
13:0000 1101
~按位取反:
~11:11110100
&按位与11&13:00001001
| 按位或11|13:00001111
^按位异或11^13:00000110 //同位取0,异位取1 (可实现交换两变量的值)
6、
“<<”左移 (乘)
11<<1:00010110 //22乘以2^1
11<<2:00101100 //4411乘以2^2 补符号位
” >>”右移 (除)
11>>1:00000101 //511/2=5
11>>2:00000010 //211/4=2
-1>>1:还是-1;
移动位数不能大于数据长度且不能小于0
7、自加自减:
前置(++)i; 后置(i)++

int i =10; 
int j = i++;//i=11 , j =10;(temp = i;j = temp;i++)
int j = ++i;//i=11 , j=11 ;(i=i+1;j=i)

首先,编辑器按左至右读符号,如果有两句连续的符号能合成一个运算符,编缉器会将这两符号读成一个运算符(贪心法),例如:

int a = 0;
int b = 1;
int y = a+++--b;
y = a+++--b;//这句,依次读+++ --
y = a+++--b;//拆开来是:y=(a++)+(--b);

(a++)是先运算,后自加。(–b)是先自减,后运算。所以a++的值是0,–b的值也是0。
y=0+0;
在给y赋完值后,a的值是1
8、整数除法截断运算:
假定我们让a除以b,商为q,余数为r:
q = a / b ;
r = a % b ;
在这里我们不妨假定b大于0,我们希望a、b、q、r之间维持三点关系,
①最重要的一点,q*b+r = a要成立,余数的定义
②如果我们改变a的正负号,我们希望这会改变q的符号,但这不会改变q的绝对值。
③当b>0时,我们希望保证r>=0,且r < b
C语言在实现证书除法截断运算时,必须放弃上述三条原则中的一条。大多数程序设计语言选择放弃第3条,而改为要求余数与被除数的正负号相同;
根据性质2,我们可以在被除数与除数异号的情形下,将它们转化为绝对值进行计算,最后根据性质3,确定商与余数的符号。毕竟商的绝对值是不变的。
9、运算符优先级别:
https://m.jb51.net/show/37282

三、预处理
1、#define宏定义:“替身”,作用范围为本行宏定义开始至#undef
定义字符串用于存放路径;
宏定义表达式,但需避免歧义;
定义的宏介于#define之后的空格以及紧跟之后的空格之间,其余为宏表示内容
2、#undef撤销宏定义
3、条件编译:
if格式

#if 表达式
    语句序列1#else
    语句序列2#endif

功能:当表达式的值为真时,编译语句序列①,否则编译语句序列②。其中,#else和语句序列②可有可无。

ifdef格式

#ifdef 标识符
    语句序列1#else
    语句序列2#endif

功能:当标识符已被定义时(用#define定义),编译语句序列①,否则编译语句序列②。其中#else和语句序列②可有可无。

ifndef格式

#ifndef 标识符
    语句序列1#else
    语句序列2#endif

功能:该格式功能与ifdef相反。
4、#error编译错误消息提示并停止编译
5、#include文件包含(多个源文件连接成一个),找到后文件内容替换该语句

#include<>
#include"自定义"

6、#line改变当前行数与文件名,避免被中间文件代替
7、#pragma设定编译器的状态或者是指示编译器完成一些特定的动作
其格式一般为:
#pragma para //其中para为参数
message参数:它能够在编译信息输出窗口中输出相应的信息,当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来
code_seg参数:它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它
once参数:只要在头文件的最开始加入这条指令就能够保证头文件被编译一次
hdrstop参数:表示预编译头文件到此为止,后面的头文件不进行预编译
resource参数:表示把.dfm文件中的资源加入工程。.dfm中包括窗体 ,外观的定义
warning参数:

#pragma  warning( push )//保存所有警告信息的现有的警告状态
#pragma  warning( push, n )//保存所有警告信息的现有的警告状态,并且把全局警告等级设定为n   
#pragma  warning( pop )//向栈中弹出最后一个警告信息,在入栈和出栈之间所作的一切改动取消

comment参数:该指令将一个注释记录放入一个对象文件或可执行文件中
pack参数:字节对齐问题

#pragma pack(n)//编译器按n字节对齐
#pragma pack( )//编译器取消按自定义字节对齐

8、#运算符
将字符以宏参数显示
9、##预算符
即 #(#运算符) ,此时外侧的#相当于粘合剂
10、内存对齐问题:
原因http://blog.csdn.net/l_tudou/article/details/51999765
原则http://blog.csdn.net/tingyun_say/article/details/51443803

四、指针和数组
指针
1、*
* 乘法;指针变量声明;解引用(指针间接引用)

int a =10; 
int *p = &a; 
*p = 20;//解引用,跳到那块内存去

“*”前面的数据类型只说明指针所指向的内存里存储的数据类型,指针大小均为4byte(32位)
2、NULL:宏定义为0

int *p = NULL;//定义并初始p

int *p;
*p = NULL;//给p指向的内存赋值NULL(不确定)

int i = 2;
int *p = &i;//p指向i的地址
*p = 1;//p指向内存的值改为1,即i=1

3、指定内存写入指定数据:

int *p = (int *)指定地址;//强类型转换,为确认可访问内存可先定义一变量查询其地址
*p = 指定地址;

4、访问:

*(p+4)  = p[4];// p存储的地址值基础上加4个字符偏移量

5、二级指针:
保存一级指针的地址 ,而一级指针保存指向数据的地址,即二级指针实质指向数据

数组
1、与sizeof:
计算数组长度(数组元素个数):

int len = sizeof(arr) / sizeof(arr[0]);//arr占用内存长度除以其首元素字节长度
int arr[5];
&arr;//int (*)[4];也就是指向包含4个int数据的数组的指针。
sizeof(arr);//20//整个数组所占内存长度
sizeof(&arr);//4//整个数组的地址的占用空间长度
sizeof(arr[0]);//4
sizeof(arr[5]);//4//预编译,仅判断类型计算占用字节大小
sizeof(&arr[0]);//4
sizeof(&arr[5]);//4

2、数组名
①代表数组首元素首地址(&a[0]),因此数组名可作右值
②数组不能整体赋值,因此数组名不能作左值

作为数组名代表整个数组;
作为指针代表数组的首元素地址(数组首元素的首地址);
arr,&arr 同值不同作用域
&arr[i]和arr+i含义相同,即arr+i是arr之后第i个元素地址

sizeof(arr);//整个数组所占内存长度
sizeof(&arr);//整个数组的地址的占用空间长度
arr+1;//移动一个元素后的长度//arr[1]
&arr+1;//跨过arr数组的下一个地址

3、访问:
(a+4) = (p+4);
a[4];//数组首元素首地址的基础上加4个元素偏移量
4、指针数组 与 数组指针

int *p[4];//定义p为指针数组,包含4int型指针变量的一维数组

int (*p)[i];//定义p为一个指针变量,指向包含i个int型元素的一维数组

5、多维数组:
令指针变量p指向数组
(1)某元素,由一维数组可推导,

a[i]+j = *(a+i)+j = &a[i][j]
*(a[i]+j) = *(*(a+i)+j) = a[i][j]

(2)某行,

*(a+i) = a[i] = p[i] :第i行的指针,即&a[i][0]

6、一维数组作函数参数,编译器解析为指向首元素首地址的指针
一级指针作函数参数,拷贝传递,而不是传递指针本身

函数指针:入口地址(存储空间起始地址)
(1)函数名调用函数
(2)通过指针变量访问所指向的函数
定义:类型名 (*指针变量名)(函数参数表列)
调用:指针变量名=函数名

    p = max;//p指向函数入口地址
    p = max(a,b);//赋给p函数值
    c = (*p)(a,b)

作函数参数:形参,将实参函数的地址作为入口参数传递到其他函数
返回指针值的函数:即返回地址,可快速跳转至符合条件的位置
类型名 *函数名(参数表列)
(void(* )())0;//将0强制转化为函数指针类型,一个函数存在于首地址为0的一段区域内
(3)函数指针数组:

char * (*pf[3])(char * p);

它是一个数组,数组名为 pf,数组内存储了 3 个指向函数的指针。这些指针指向一些返回值类型为指向字符的指针、参数为一个指向字符的指针的函数。

五、内存管理
1、静态区:保存自动全局变量和 static 变量(包括 static 全局和局部变量)。静态区的内容
在总个程序的生命周期内都存在,由编译器在编译的时候分配。

栈:保存局部变量。栈上的内容只在函数的范围内存在,当函数运行结束,这些内容
也会自动被销毁。其特点是效率高,但空间大小有限。

堆:由 malloc 系列函数或 new 操作符分配的内存。其生命周期由 free 或 delete 决定。
在没有释放之前一直存在,直到程序结束。其特点是使用灵活,空间比较大,但容易出错。

2、野指针 :
“野指针”不是NULL指针,是指向被释放的或者访问受限的垃圾内存的指针。
3、常见内存错误:
http://blog.csdn.net/vcayi/article/details/1636491
内存释放:
free(p)后应立即给p置空;
函数返回栈内存时应明确栈上变量的生命周期;
对象之间调用关系尽量简洁明了,逻辑性强

六、函数
1、每一个函数都必须有注释,定义结束之后以及每个文件结束之后都要加一个或若干个空行
2、函数体内,变量定义与函数语句之间要加空行,除逻揖上密切相关的语句之间外均加空行
3、注释:
复杂的函数中,在分支语句,循环语句结束之后需要适当的注释;
修改别人代码的时候不要轻易删除别人的代码,应该用适当的注释方式
4、缩进:
用缩行显示程序结构,使排版整齐,缩进量统一使用4个字符(不使用TAB
缩进);
同层次的代码在同层次的缩进层上;
长表达式要在低优先级操作符处划分新行,操作符放在新行之首(以便突
出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读;
在函数体的开始、结构/联合的定义、枚举的定义以及循环、判断等语句中
的代码都要采用缩行
5、代码行最大长度宜控制在80 个字符以内,较长的语句、表达式等要分成
多行书写
6、不要编写太复杂的复合表达式,尽量避免含有否定运算的条件表达式
7、参数:
如果函数中的参数较长,则要进行适当的划分;
参数的书写要完整,如果函数没有参数,则用void 填充;
参数命名要恰当,顺序要合理,一般地,应将目的参数放在前面,源参数放在后面;
如果参数是指针,且仅作输入参数用,则应在类型前加const,以防止该指针在函数体内被意外修改;
尽量不要使用类型和数目不确定的参数,参数量要少
8、返回值:
不要省略返回值的类型,如果函数没有返回值,那么应声明为void 类型。如果没有返回值,编译器则默认为函数的返回值是int类型;
return 语句不可返回指向“栈内存”的“指针”,因为该内存在函数体结
束时被自动销毁;
函数名与返回值类型在语义上不可冲突
9、功能单一,规模要小,避免“记忆”
10、要检查输入参数的有效性、通过其它途径进入函数体内的变量的有效性,例如全局变量、文件句柄等
用 assert 宏做入口校验,在函数开始处检验传入参数的合法性
https://www.cnblogs.com/ggzss/archive/2011/08/18/2145017.html
11、strlen函数:
strlen() 函数计算的是字符串的实际长度,遇到第一个’\0’结束

头文件:#include <string.h>
其原型为:  unsigned int strlen (char *s);//s为指定的字符串

strlen()用来计算指定的字符串s 的长度,不包括结束字符”\0”,返回字符串s 的字符数
http://m.biancheng.net/cpp/html/167.html

七、文件结构
1、每个头文件和源文件的头部必须包含文件头部说明和修改记录
2、各个源文件必须有一个头文件说明,以及源文件书写顺序
3、需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部
4、文件名命名的规则:
文件标识符分为两部分,即文件名前缀和后缀。文件名前缀的最前面要使用范围限定符——模块名(文件名)缩写;
采用小写字母命名文件,避免使用一些比较通俗的文件名,如:public.c等

猜你喜欢

转载自blog.csdn.net/qq_39191122/article/details/79674675
今日推荐