现在的位置: 首页 > 综合 > 正文

C语言深度解剖 关键知识总结

2018年06月06日 ⁄ 综合 ⁄ 共 4686字 ⁄ 字号 评论关闭

偶然看到一本好书《C语言深度解剖》 内容不多,总共100多页但是内容挺有意思的而且通熟易懂。为了避免遗忘,现将重要的知识点做个小结归纳:

第一章 关键字


1、sizeof --是关键字 不是函数

2、声明和定义的区别 -- 定义创建了对象并为这个对象分配了内存,声明没有分配内存

3、register :请求编译器尽可能将变量存在cpu内存寄存器中(不能用&来获取register变量的地址 可能不存在内存中)

4、static :存在内存中的静态区

5、32位操作系统下:

short  --  2 byte

int       --  4 byte

long    -- 4 byte

float    -- 4 byte

double-- 8byte

char    -- 1byte 

6、 int i;

      sizeof i;  对

      sizeof int; 错

      sizeof ( int ) ;对

7、

int main()
{
    char a[100];
    int i;
    for(i=0;i<1000;i++)
   {
       a[i]=-1-i;
    }
    printf("%d",strlen(a));
    return 0;
}

两个原则: ①、原码+  == 补码-    ②、上述+、-是不考虑符号位的+、-

8、if中指针的写法:  if(NULL==p)

9、c语言中不加返回值类型的函数会被作为返回整型处理

10、const修饰指针----先忽略类型名,const离谁近就修饰谁

第二章 符号

1、不用第三个临时变量交换两个变量的值:a=a^b; b=a^b; a=a^b;

2、左移和右移的位数不能大于数据的长度,不能小于0。

3、花括号的作用就是打包,使之形成一个整体,并与外界绝缘

4、逗号表达式,i在遇到每个逗号后,认为本计算单位已经结束

int x,i=3;

x = (++i, i++, i+10);

x=15

5、C 语言有这样一个规则:每一个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是,从左到右一个一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。这个处理的策略被称为“贪心法”。需要注意到是,除了字符串与字符常量,符号的中间不能嵌有空白(空格、制表符、换行符等)

6、一些容易出错的优先级问题:

第三章 预处理


1、ANSI 标准定义的C 语言预处理指令:



2、#define 可以出现在代码的任何地方,从本行宏定义开始,以后的代码就就都认识这个宏了;也可以把任何东西


用define 宏定义表达式:

#define SQR(x) x * x

#define SUM(x) (x)+(x)

#define SUM (x) (x)+(x) 编译器以为: (x) (x)+(x)  所以在宏定义时注意空格

3、条件编译定义成宏。

条件编译的功能使得我们可以按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件。这对于程序的移植和调试是很有用的。条件编译有三种形式:

第一种形式:
#ifdef 标识符
程序段1
#else
程序段2
#endif

第二种形式:
#ifndef 标识符
程序段1
#else
程序段2
#endif

第三种形式:
#if 常量表达式
程序段1
#else
程序段2
#endif

4、字节对齐

①、数据类型的自身对齐值:对于char为1,short为2,int float  double 为4

②、结构体的自身对齐值:成员自身对齐值中最大的那个

③、指定对齐值:#pragma pack (n),编译器将按照n 个字节对齐

step1:算出结构体自身对齐值(最大的那个)

step2:对每个元素求自身对齐值与结构体自身对齐值比较  取小的那个作为N----->有效对齐值

step3:对每个元素地址%N=0

step4:最后总长度%结构体自身对齐值=0

第四章 指针和数组


1、我们可以简单的这么理解:一个基本的数据类型(包括结构体等自定义类型)加上“*”号就构成了一个指针类型的模子。这个模子的大小是一定的,与“*”号前面的数据类型无
关。“*”号前面的数据类型只是说明指针所指向的内存里存储的数据类型。所以,在32 位系统下,不管什么样的指针类型,其大小都为4byte。

2、在使用NULL 的时候误写成null 或Null 等。这些都是不正确的

3、很多初学者弄不清指针和数组到底有什么样的关系。我现在就告诉你:他们之间没有任何关系!只是他们经常穿着相似的衣服来逗你玩罢了。
指针就是指针,指针变量在32 位系统下,永远占4 个byte,其值为某一个内存的地址。指针可以指向任何地方,但是不是任何地方你都能通过这个指针变量访问到。
数组就是数组,其大小与元素的类型和个数有关。定义数组时必须指定其元素的类型和个数。数组可以存任何类型的数据,但不能存函数。

4、说以下标的形式访问在本质上与以指针的形式访问没有区别,只是写法上不同罢了。

5、指针和数组根本就是两个完全不一样的东西。只是它们都可以“以指针形式”或“以下标形式”进行访问。一个是完全的匿名访问,一个是典型的具名+匿名访问。

6、指针和数组的区别总结:

7、数组

a[1000 ]

a 不能作为左值!这个错误几乎每一个学生都犯过。编译器会认为数组名作为左值代表的意思是a 的首元素的首地址,但是这个地址开始的一块内存是一个总体,我们只能访问数组的某个元素而无法把数组当一个总体进行访问。所以我们可以把a[i]当左值,而无法把a当左值。其实我们完全可以把a 当一个普通的变量来看,只不过这个变量内部分为很多小块,我们只能通过分别访问这些小块来达到访问整个变量a 的目的。

8、指针数组和数组指针

指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在32 位系统下永远是占4 个字节,至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。

9、

#include <stdio.h>
intmain(int argc,char * argv[])
{
int a [3][2]={(0,1),(2,3),(4,5)};
int *p;
p=a [0];
printf("%d",p[0]);
}
问打印出来的结果是多少?
很多人都觉得这太简单了,很快就能把答案告诉我:0。不过很可惜,错了。答案应该是1。如果你也认为是0,那你实在应该好好看看这个题。花括号里面嵌套的是小括号,而
不是花括号!这里是花括号里面嵌套了逗号表达式!其实这个赋值就相当于int a [3][2]={ 1, 3,5};

10、二维数组本质上还是一维数组

void fun(char a[10])
{
int i = sizeof(a);
char c = a[3];
}
如果数组b 真正传递到函数内部,那i 的值应该为10。但是我们测试后发现i 的值竟然为4!为什么会这样呢?难道数组b 真的没有传递到函数内部?是的,确实没有传递过去,
这是因为这样一条规则:
C 语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。

也就是说传入堆栈的只是一个指针而不是整个数组。

这么做是有原因的。在C 语言中,所有非数组形式的数据实参均以传值形式(对实参做一份拷贝并传递给被调用的函数,函数不能修改作为实参的实际变量的值,而只能修改
传递给它的那份拷贝)调用。然而,如果要拷贝整个数组,无论在空间上还是在时间上,其开销都是非常大的。更重要的是,在绝大部分情况下,你其实并不需要整个数组的拷贝,你只想告诉函数在那一刻对哪个特定的数组感兴趣

C 语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。这条规则并不是递归的,也就是说只有一维数组才是如此,当数组超过一维时,将第一维改写为指向数组首元素首地址的指针之后,后面的维再也不可改写。比如:a[3][4][5]作为参数时可以被改写为(*p)[4][5]。


11、数组指针

int (*)[10] p;

12、函数指针

char * (*fun1)(char * p1,char * p2);这里fun1 不是什么函数名,而是一个指针变量,它指向一个函数。这个函数有两个指针类型的参数,函数的返回值也是一个指针。

函数指针使用的例子:
上面我们定义了一个函数指针,但如何来使用它呢?先看如下例子:

#include <stdio.h>
#include <string.h>
char * fun(char * p1,char * p2)
{
int i = 0;
i = strcmp(p1,p2);
if (0 == i)
{
return p1;
}
else
{
return p2;
}
}
int main()
{
char * (*pf)(char * p1,char * p2);
pf = &fun;
(*pf) ("aa","bb");
return 0;
}

*(int*)&p=(int)Function;表示将函数的入口地址赋值给指针变量p。

使用函数指针的好处在于,可以将实现同一功能的多个模块统一起来标识,这样一来更容易后期的维护,系统结构更加清晰。或者归纳为:便于分层设计、利于系统抽象、降低耦合度以及使接口与实现分开。

第五章 内存管理


1、

静态区:保存自动全局变量和static 变量(包括static 全局和局部变量)。静态区的内容在总个程序的生命周期内都存在,由编译器在编译的时候分配。
栈:保存局部变量。栈上的内容只在函数的范围内存在,当函数运行结束,这些内容也会自动被销毁。其特点是效率高,但空间大小有限。
堆:由malloc 系列函数或new 操作符分配的内存。其生命周期由free 或delete 决定。在没有释放之前一直存在,直到程序结束。其特点是使用灵活,空间比较大,但容易错。


2、结构体指针未初始化

struct student
{
char *name;
int score;
}stu,*pstu;
intmain()
{
strcpy(stu.name,"Jimy");
stu.score = 99;
return 0;
}

这里定义了结构体变量stu,但是他没想到这个结构体内部char *name 这成员在定义结构体变量stu 时,只是给name 这个指针变量本身分配了4 个字节。name 指针并没有指向一个合法的地址,这时候其内部存的只是一些乱码。所以在调用strcpy 函数时,会将字符串"Jimy"往乱码所指的内存上拷贝,而这块内存name 指针根本就无权访问,导致出错。解决的办法是为name 指针malloc 一块空间。

3、malloc函数

函数原型:(void *)malloc(int size)

malloc 函数的返回值是一个void 类型的指针,参数为int 类型数据,即申请分配的内存大小,单位是byte。内存分配成功之后,malloc 函数返回这块内存的首地址。你需要一个指针来接收这个地址。但是由于函数的返回值是void *类型的,所以必须强制转换成你所接收的类型。

char *p = (char *)malloc(100);

使用malloc函数同样要注意这点:如果所申请的内存块大于目前堆上剩余内存块(整块),则内存分配会失败,函数返回NULL。

malloc 函数申请的是连续的一块内存

4、用malloc 函数申请0 字节内存

另外还有一个问题:用malloc 函数申请0 字节内存会返回NULL 指针吗?可以测试一下,也可以去查找关于malloc 函数的说明文档。申请0 字节内存,函数并不返回NULL,而是返回一个正常的内存地址。但是你却无法使用这块大小为0 的内存。这好尺子上的某个刻度,刻度本身并没有长度,只有某两个刻度一起才能量出长度。

第六章 函数


1、










第七章 文件结构


 1、


抱歉!评论已关闭.