C语言深度剖析

来源:互联网 发布:迪奥西斯形态数据 编辑:程序博客网 时间:2024/05/17 08:27

记录一些记忆不清楚的知识点,都是基础的东西,必须要牢记!!!


1)register变量,必须是被cpu所接收的变量类型,必须是一个单个的值,长度应该小于或等于int。register变量可能不放在内存中,所以用&来取其地址是不行的。


2)计算机中,数值一律使用补码来存储,因为补码可以处理符号和负数。


3)布尔变量与0比较:bool bFlag = false; if (bFlag) ...... if ( !bFlag)......

浮点数与0比较:float fValue = 0.0; if ( (fValue <= 0.00000001) && (fValue >= -0.00000001) )


4)判断指针是否为空:if (p == NULL),为防止粗心写成:if (p = NULL),

一般会这么写:if (NULL == p),少写一个=,会出现编译错误,立马发现。


5)switch/case语句,case语句后最好加break,防止分支重复;最后必须使用分支default,即使不需要也写出来。(可以检查判断语句是否出错)

case后面只能是:整型、字符串常量、常量表达式。

case语句的排序:按数字或字母顺序。正常情况放前面,异常情况放后面。按执行频率排列。

不要为case语句特意制造一个变量。


6)return : 语句“return;”是没有任何问题的,表示不返回任何值,仅结束函数。


7)const:“只读”、“readonly”。const变量在程序运行过程中只有一份拷贝(因为他是全局只读变量,存在静态区)

8)


9)最易变的关键字:volatile(易变的,不稳定的)

类型修饰符,用它修饰变量表示可以被某些编译器未知的因素修改(比如操作系统、硬件、其他线程等)。

volatile int i = 10;他告诉编译器,变量i随时会发生变化,所以每次使用他的时候必须从内存中去取。


10)extern:可以置于变量和函数前,以表示变量和函数的定义在别的文件中


11)struct结构体,一个空的结构体在vc中:sizeof(),打印出来的结果是1而不是0。

编辑器认为任何一种数据类型都应该有大小,最小的数据成员char需要1个字节,所以编辑器为每个结构体类型保留最少1个字节大小的空间。

>>>柔性数组:结构体中允许最后一个元素是未知大小的数组,但其前面必须最少有一个变量。sizeof()来表示结构体大小时,是不包括这个数组的,柔性数组的目的是为了可以定义一个可变长度的结构体。包含柔性数组的结构体,使用malloc来动态分配内存,分配的内存大小应该大于结构体的大小,以适应柔性数组预期的大小。


12)union共用体:union所有数据成员共用一个空间,同一时间只能存储其中一个数据成员,所有的数据成员具有相同的其实地址。

>>>关于共用体,有这么一个题目,跟内存有关,与大小端模式有关,值得看一下:


分析:a数组中是怎么存这两个数的了:39 38(小端模式 ),所以当你读i的时候其实读出来的数是:3839,转化为10进制,结果是3*16^3+8*16^2+3*16^1+9*16^0 = 14393


>>>关于大小端内存中数的存储,还有一个值得看的例子:

int a[5] = {1, 2, 3, 4, 5};int *p1 = (int *)(&a + 1);printf("%x\n", p1[-1]);int *p2 = (int *)((int)a + 5);printf("%x\n", *p2);
分析:

第一个很好理解,p1是按数组大小为单位来移动的,所以p1[-1]应该是5;

第二个,我们先来看看数组a中的5个数在内存中是如何存储的(用16进制来表示)

a:01 00 00 00    02 00 00 00    03 00 00 00    04 00 00 00    05 00 00 00

int占4个字节,每个字节8位,16进制正好用2位来表示。

p2在数值上加5,即向后移动5个字节,指向红色标记的位置。从该位置起连续4个字节即为我们需要的结果。

这里是小端模式,所以00 00 00 03实际上是03 00 00 00。当然打印出来第一个0是没有的:3000000。

注意:打印格式是16进制,所以是这个结果,如果要求%d格式,结果讲转换成10进制,一个很大的数。

一般表示内存画这种内存图的时候,使用16进制比较好,进制间的转化也比较方便。


>>>再来一题关于内存的:

struct Test{int num;char *pcName;short sDate;char cha[2];short sBa[4];}*p;
假设p的值为:0x100000,求下列表达式的值:
p + 0x1 = (  );
(unsigned long)p + 0x1 = 0x(  );
(unsigned int *)p + 0x1 = 0x(  );

分析:

sizeof(Test) = 20字节,这个跟内存对齐有关,所以第一问就想当一个指针以20字节为单位移动了一次,

0x100000 + 0x14(20是10进制数) = 0x100014

第二问设计强制类型转换,转换后按数值来相加:即为0x100001;

第三问将p转换为无符号整型指针,指针移动单位为sizeof(int)(4个字节),所以结果为:0x100004;


13)sizeof(enum) = 4


14)typedef:根据他的作用应该叫“typerename”,他是给一个已存在的数据类型取一个别名,而非定义个新类型。


15)对指针进行+1操作,并不是在地址值上直接+1。比如一个类型为T的指针,则以sizeof(T)为移动单位。


16)指针数组与数组指针{可以根据优先级来区分

int *p1[10],指针数组,因为[ ]比*优先级要高,所以他表示定义一个名为p1、包含10个指向int型数据的指针的数组。


int (*p2)[10],数组指针,()优先级在这里最高,*和p2构成一个指针,int修饰的是数组里的每个元素,而数组并没有取名,是一个匿名数组。所以这里表示,定义一个指针,指向一个数组,这个数组包含10个int数据。

()和[ ]优先级是一样的,都是第一级里面的,根据结合性自左向右



17)二维数组

比如有int a[3][4],在内存中他是怎样的了?见下图:



所以a[i][j]的地址 = &a[i] + j*sizeof(int)


最简单是是画一个内存图出来,结果一目了然:



关键是要看数组指针到底表示的是什么,指向的是什么。


18)函数本身并没有类型,其类型表示的是函数返回值的类型。


19)c语言中,一维数组作为函数参数时,编译器总是把他解析为一个指向其首元素首地址的指针,超过一维时则不可以这么理解。


20)函数指针



(int*)&p:表示将地址强制转换成指向int类型的指针

(int)Function:表示将函数的入口地址强制转换成int类型的数据

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

所以,(*p)()表示对函数的调用

来个难点的,解释一下:(* ( void(*)() )0 )()

void(*)():一个函数指针类型,指向的函数无参数,没有返回值

( void(*)() )0:将0强制转换为该函数指针类型,由于0是地址,也就是说函数存在地址为0的区域里

(* ( void(*)() )0 ):取地址为0的区域里的的内容

(* ( void(*)() )0 )():函数调用


21)函数指针数组

例如:char *(*pf[3])(char *p)

它表示一个数组,数组名为pf,数组里有三个元素,每个元素都是函数指针,指向带有一个char*指针参数并返回char*类型的函数。这里的关键点是:它是一个数组!!!!!!!!!!!!!!


22)函数指针数组指针(听起来有点晕)

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

这里pf是一个指针,指向一个数组(匿名),数组有三个元素,每个元素都是函数指针,指向的函数有一个参数char*并返回char*。

#include <iostream>using namespace std;//测试 函数指针数组char *fun1(char *p){printf("%s\n", p);return p;}char *fun2(char *p){printf("%s\n", p);return p;}char *fun3(char *p){printf("%s\n", p);return p;}int main(){//测试函数指针数组指针char * (*pfa[3])(char *p);char * (*(*ptr)[3])(char *p);ptr = &pfa;pfa[0] = fun1;//可以直接使用函数名pfa[1] = &fun2;//可以用函数名加地址符pfa[2] = &fun3;//函数调用(*ptr)[0]("fun1");//可以这么写ptr[0][1]("fun2");//也可以这么写ptr[0][2]("fun3");//还是有点晕return 0;}


23)不管什么时候使用指针一定要确保指针是有效的。一般在函数入口处使用:assert(p != NULL);对参数进行校验;在非参数的地方使用:if (p != NULL)来校验。

用完指针后最后将其赋值为NULL,防止“野指针”(即悬垂指针)的出现。


24)使用malloc申请0字节的内存,并不返回NULL,而是返回一个正常的内存地址,只不过你无法使用。