-1:  形参中的数组形式

与其他地方定义的数组

形参中的数组形式 都会被转换为 指针模式

如下:

void CSGetFileName(char pcName[10], char aData[], char *

pcTmp)

{

printf("sizeof pcName = %d, %sn",sizeof(pcName),pcName);

printf("sizeof aData = %d, %sn",sizeof(aData),aData);

printf("sizeof pcTmp = %d, %sn",sizeof(pcTmp),pcTmp);

}

sizeof pcName = 4, ni hao

sizeof aData = 4, wo hao

sizeof pcTmp = 4, da jia hao

其他地方定义的数组,adata[] 是数组的维数省略的形式(前提是要在定义时初始化,否则编译器不知道他的维数,就报错)

void tst()

{

char * pdata = "hello";

char adata[] =

"adfadf";

char sadata[10];

printf("sizeof pdata = %d,

%sn",sizeof(pdata),pdata);

printf("sizeof adata = %d,

%sn",sizeof(adata),adata);

printf("sizeof sadata =

%dn",sizeof(sadata));

}

sizeof pdata = 4, hello

sizeof adata = 7, adfadf

sizeof sadata = 10

=================================

0:指针变量

指向某种类型的一个变量;指针变量指向内存中某类型所拥有的一个单元空间,

该单元空间有可能是用该类型定义的数组中某一个元素的空间,也有可能是仅仅定义了一个变量所拥有的空间;所以可以灵活使用,可以拿该指针在一片内存中连续地移动。【简言之:指针指向的内存空间,有可能是数组的连续空间,也有可能仅是单个变量空间】

int

*

int * *

char *

char *

*

1:C语言中

main函数 有两种方式

1:int main(int argc,

char *argv[])

2: int main(void)

其他形式都是错误的,main函数是需要带一个int类型的返回值得;比如void

main(void)【编译器报错】,main函数要通过一个返回值表示运行情况,返回0表示正常运行后结束。

2 :什么是指针?与地址有什么关系?

指针、指针变量:它是一个变量,该变量具有类型属性【该属性表现为他的值加1时,内存地址相应加

sizeof(指针类型) * 1】,且它的值是一个内存地址;

3 :魔数 MagicNum

所谓魔数和魔字符串就是指在代码中出现但没有解释的数字常量或字符串。

在程序中,有时通过魔数给某个对象做一个特殊标识。

4 :空指针  NULL

0  ' '

定义一个指针变量,给该指针变量赋值为NULL,该指针即为空指针,有了这样的赋值初始化,在编程(尤其是JAVA)时,我们很快就会会找到这个NULL

未被初始化的空指针;

不是所有的操作系统中,NULL值都被定义为0,也有可能存在值不为0的空指针;

int * p = 0; //可以编过

int * p = 3;

//编译器报错(有些编译器可能只报为警告)

3是一个整数,int类型的,没有经过强制转换当然不能直接赋值给int

*类型的指针变量;

然而0也是一个int 整数,但是ANSI

C标准中有说: 编译器应该根据程序的上下文将0作为指针进行处理,那么这里的0就变为了空指针;所以编译通过。

win32位每个进程的地址空间里,开始内存地址空间里设置了一个分区,范围是0x00000000~0x0000ffff,如果进程中有线程试图读写这段区域,cpu就会引发非法访问的。这就是引用空指针时,CPU的保护机制

5 :void * 类型指针变量

void即“无类型”,void

*则为“无类型指针”,可以指向任何数据类型。于是乎,任何类型的指针都可以赋值给void

*型的指针变量;反过来,由于void

*是无类型的指针,他不能直接赋值给其他类型的指针变量,需要经过强制转换。

6 :指针的运算操作

指针可以进行加减操作,int *p; p++

p--  p +=

1;

单个指针在做加减法时,在计算内存地址时,一定要考虑该指针的类型

两个指针可以进行相减的操作:

int a = 1;

int b = 2;

int * p = &a;

int * q = &b;

printf("%dn",p -q);

两个指针不能进行相加的操作(无意义):编译器报错

printf("%dn",p + q);

指针可以进行加运算,但是指针与指针之间不能直接相加

7 :数组

在引用时,数组名一般情况下 是 该数组第一个元素的地址;

在数组引用时,[]对编译器来说没有用,a[x]这种形式在编译时都会被转换为*(a +

x)的形式进行计算的;

所以在引用时,如一个数组int

a[5],在引用时,直接b = a[3]; 也可以写为b = 3[a];

都是等价的,可以编过的,因为编译时,都转为*(a + x)的形式

8 :数组声明

重点:C中,当数组出现在表达式中的时候,数组会立刻被编译器转换解读为指针。如a[5]=>*(a+5)

数组在声明时,一般都会对应在内存中分配空间;

然而,数组的声明 作为函数的形参时,该数组的声明会被编译器转换解读为指针形式;

如 int func(int a[10]) 等价于 int func(int a[]), 所以类似形式最终会等价于int func(int *a)所以上面函数的三种形式的形参,最终效果 形参都是一个 int型的指针变量

有了上面的分析,所以在通过形参给一个函数传递一个数组【如果是a[5] 这个是数组中单个元素的引用,

有了上面的分析,在定义形参时,声明一个形参是一个数组时,其他这时这个数组是被转换为指针形式,也就是一个指针变量,所以没法通过形参传递整个数组,你在函数的形参中定义传一个数组,其实只能传入该数组的起始地址】的时候,其实函数的形参获得的是数组的第一个元素的地址

9 :C变量

C中,变量注意三个方面: 1:变量的作用域

2:变量的存储期==》影响变量的寿命 3:malloc free特例

1:变量的作用域

全局变量、文件内部的静态变量 、 局部变量;

全局变量,在任何地方都是可见的。当程序被分割为多个源代码文件进行编译时,声明为全局变量的变量也是可以从其他源代码文件中引用的;

文件内部的静态变量, 该变量仅在该文件内的该变量的声明后的所有地方可见;

代码一定要谨慎使用全局变量,甚至要求更高些,好的代码甚至禁止全局变量,因为全局变量读写可以在任何地方进行,影响代码的可读性与健壮性;

局部变量一般被分配在栈中,局部变量只能在包含它的声明的语句块中(使用{},括起来)被引用。

2:变量的存储期

变量的存储器

与 变量的寿命 说的是差不多的内容;

拥有静态存储期的变量 叫做

静态变量:

全局变量、文件内的static变量、指定static的局部变量都拥有静态存储期,这些变量都是静态变量;静态变量一直存在于内存的同一个地址上,他的寿命从程序运行时开始,到程序关闭时结束。

拥有自动存储期的变量叫做自动变量:没有指定static的局部变量,持有自动存储期,都是自动变量,自动变量在程序运行进入他所在的语句块时被分配内存区域,该语句块执行结束后这块内存区域被释放【栈机制】。

malloc分配的内存空间(非变量了),寿命一直延续到使用free()释放它为止。

3:变量的寿命

有了存储期的讨论,其实已经讲清楚变量的寿命了;

总结之:

1:静态变量=》寿命从程序运行时到程序关闭时结束

2:自动变量=》寿命从该变量的语句块被执行结束为止

3:malloc分配的领域=》寿命到调用free()为止。

语句块 =》包围的是作用域,

static=》静态连接  extern=》外部连接

10 :存储类型修饰符

一共有typedef  extern

static auto register,其实只有static 才是真正的存储类型修饰符;

extern:

使得在其他地方(文件或者库)的外部变量或符号可以在本地可见;

auto:默认的,没事显示指定的必要;

register:可以给出编译器优化的提示,现在的编译器已经够先进,所以该关键字也可不必

typedef:它只是因为可给编译器带来便利才被归纳到存储类型修饰符中来的。

static:静态存储,只有本文件内才可见到该符号。

第二章

11 :内存空间划分

1:栈stack(有些地方又称为堆栈),由编译器自动分配释放

是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

2:堆heap

堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

3:BSS段(bsssegment),BSS是英文BlockStartedbySymbol的简称

BSS段(bsssegment)通常是指用来存放程序中未初始化的全局变量、静态变量【静态变量在本文件内而是全局变量的概念】的一块内存区域。BSS是英文BSS段属于静态内存分配。

4:数据段(datasegment)

数据段(datasegment)通常是指用来存放程序中已初始化的全局变量、静态变量的一块内存区域。数据段属于静态内存分配。

5:代码段

代码段(codesegment/textsegment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读,

某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

12 :字符串常量

在C中,字符串是作为“char类型的数组存储的”;

程序中出现的字符串常量,和代码一样,存储在内存中的代码段;如下面的"i am

ccc!"

void func1(void)

{

printf("===>%pn","i

am ccc!");

}

int main(void)

{

func1();

}

13 :函数指针

函数可在表达式中被解读成“指向函数的指针”;

函数指针变量的值 与 该指针解引用的值 是一样的, 也就是

函数指针 与

函数指针的解引用都指向函数的起始地址;

void (* pFunc)(void) =

func1;

printf("pFunc ==>%dn",pFunc);

printf("*pFunc ==>%dn",*pFunc);

即:pFunc == *pFunc

所以通过函数指针变量,调用函数时,两种方式是等价的=》 pFunc() ==

(*pFunc)()

14 :通过nm 工具查看文件中的符号

各目标代码大多数都具备一个符号表,我们可以通过nm开查看这个符号表;

Nm主要是用于查找目标文件、库文件(库文件实际上就是目标文件的打包)、elf格式可执行文件中的特定符号,这些符号主要是函数名和全局变量名。

nm

print_address.o

00000004

b file_xxxx

......

T .........

......

U .........

T:

表示该符号位于代码区text

section

U:该符号在当前文件中是未定义的,即该符号的定义在别的文件中。例如,当前文件调用另一个文件中定义的函数,在这个被调用的函数在当前就是未定义的;但是在定义它的文件中类型是T。但是对于全局变量来说,在定义它的文件中,其符号类型为C,在使用它的文件中,其类型为U。

A:该符号的值是绝对的,在以后的链接过程中,不允许进行改变。这样的符号值,常常出现在中断向量表中,例如用符号来表示各个中断向量函数在中断向量表中的位置

B:该符号的值出现在未初始化数据段(bss)中。例如,在一个文件中定义全局static

int test。则该符号test的类型为b,位于bss

section中。其值表示该符号在bss段中的偏移。一般而言,bss段分配于RAM中

C:该符号为common。common

symbol是未初始话数据段。

D:该符号位于初始化数据段中。一般来说,分配到data

section中

链接器就是根据这些信息,给这些目前为止还只是个“名称”的对象分配地址;

15 :C语言中 自动变量保存在栈中

16 :C语言的函数调用时, 参数(实参)是从后往前

压入堆栈的

C语言中,调用函数时,调用方先将实参列表从后往前依次压入堆栈,然后再将返回地址(也就是调用函数的下一行代码的执行地址)压入堆栈,这时再跳到被调函数中去执行;

C语言之所以使用从后往前

将参数压入堆栈,就是为了实现可变长参数这个功能。可变长参数见下

17 :可变长参数的实现机制

对于形参中的 ...

这种写法,编译器不对这部分参数做类型的检查。

我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的, 由于1)硬件平台的不同

2)编译器的不同,所以定义的宏也有所不同,下面以VC++中stdarg.h里x86平台的宏定义摘录如下(''号表示折行):

typedef char * va_list; //定义一个即将从堆栈 获取已压入堆栈的 一个实参

#define _INTSIZEOF(n) ==> ((sizeof(n)+sizeof(int)-1) &

~(sizeof(int) - 1) )

//定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.

#define va_start(ap,v) ==>( ap = (va_list)&v + _INTSIZEOF(v)

)

//声明变量

va_list ap; 这里 通过赋值,使ap为 调用方的函数接口中实参v的相邻后一个实参在堆栈中的地址

#define va_arg(ap,t) ==>( *(t *)((ap += _INTSIZEOF(t)) -

_INTSIZEOF(t)) )

//这句话将压入堆栈中的一个实参值取出,同时ap指向下一个堆栈中的下一个实参

#define va_end(ap) ( ap = (va_list)0 )

C语言的函 数是从右向左压入堆栈的,图(1)是函数的参数在堆栈中的分布位置.我

们看到va_list被定义成char*,有一些平台或操作系统定义为void*.再

看va_start的定义,定义为&v+_INTSIZEOF(v),而&v是固定参数在堆栈的

地址,所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在堆 栈的地址

18

:断言assert()

assert()在assert.h中;

assert(条件表达式); 若条件表达式的结果为真,什么都不会发生;若条件表达式为假,则会输出相关信息并且强制终止程序;

19

:堆的定义

能够动态地(运行时)进行内存分配,并且可以通过任意的顺序释放的记忆区域,称为堆heap

20 :在标准ANSI

C中,不同类型的指针不必强制转换,可以直接赋给另一类型的指针变量【可以记忆malloc函数申请空间后可以不强制转换直接将void

*赋给另一类型的指针变量,因被赋值的指针变量有类型属性】

void * pvData = (void

*)0x123456;

char * pcData = pvData; OK

pvData = pcData;  OK

【这里不强制转换,编译时会报出警告】

C++

中,可以将任意的指针赋给void *类型的指针变量,但不可以将void *

类型的值赋给通常的指针变量。所以在C++中,malloc()的返回值必须要进行强制转换。但C++中,通常使用new来动态分配内存

21:可选择性 关闭打印的控制方法

#define Debug

1?(0):printf

==> Debug("xxxxn"); ==>

1?(0):printf("xxxxn");

22:malloc的返回值

malloc的返回值是void *类型的;目前C,是不必强制转换就可以直接将void *

赋给其他类型的指针变量。

23:malloc是系统调用吗?

什么是系统调用?什么是标准函数库调用?系统调用就是调用操作系统相关的函数。标准库函数就是调用标准库中的函数,在各个平台上标准函数库中的各个接口是一致的,跨平台特性好。而系统调用与特定的操作系统相关,移植性差。但是标准函数调用最终是通过底层的系统函数接口来实现的。

malloc不是系统调用,而是标准函数调用,所以我们在不同的平台上,都可以调这个接口,接口一样且实现相同的功能。

24:malloc分配内存的机制

malloc()一般从操作系统一次性取得比较大的内存,然后将这些内存零售给应用程序。

不同的操作系统,malloc拿到内存的方法不一样。在unix中,malloc调用brk()该系统调用函数接口去申请内存。

系统调用brk()函数,通过设定这个内存区域的末尾地址,来伸缩内存空间。

上图中的地址 高低 是不一定的,看具体的系统

用了malloc来分配空间,就一定要在使用完后用free释放空间【java由于有垃圾收集机制,所以java中不需要人工去释放分配了的空间】

25:内存管理机制浅谈

最简单的方式: 链表管理

每个块包含一个管理区域以及一个数据区域;各个块通过管理区域练成链表进行管理;

具体的方式是:malloc遍历该链表来寻找空的块,如果发现尺寸大小能满足使用的块,就分割出来将其变成使用中的块。free则是将管理区域的标志置为“空块”,顺便也可以将上下空块合并成一个块,这样来防止碎片化。如果没找到足够大的块,就请求操作系统进行空间的扩展。brk()

频繁使用malloc易导致内存碎片。

另一种方式: buddly block

system 将大的内存逐步对半分开,虽然速度快,但是容易造成内存的使用率下降。

....

26:free之后,内存空间的使用权已移交给操作系统,可供其他地方使用,但该空间的数据还未破坏,但这时不应该再通过指针去访问这个空间。

27:内存碎片化

内存被零零碎碎分割,会出现很多细碎的空块,并且这些空块是无法被利用的。这种现象就是碎片化。

在C中,只要使用malloc函数,就无法从根本上回避碎片化的问题,C中,直接将虚拟地址交给应用空间,库的一方是没有办法去随意移动内存区域,而达到整个碎片的功能。

28:内存泄露的统计方法

计数法:我们可以将malloc 、free 与我们的计数变量

各自封装成一个函数,通过这样的计数法可以简单的防止与统计内存泄露。

29:内存对齐

编译器会适当根据CPU的特性

对存储的边界进行调整,以使CPU在访问操作过程中,达到较高的读写速度。

这时在结构体中就有可能会插入合适的填充物,以达到有效数据的存储地址

(存储边界)符号CPU快速访问的目的。这时通过sizeof获得结构体的大小是包含了填充物的空间大小的。

根据CPU的情况,对齐的方式不一样,有的double

可被配置在4的倍数的地址上,有些平台double需要配置在8的倍数的地址上。

30:字节排序

字节排序讲的就是

在内存中,存放整数时的大小端模式。

第三章:解密C的语法

31:C中数据类型=> 基本类型 与 派生类型

派生类型:

从基本类型派生出来的类型。

如 基本类型int, 他的派生类型 int * , int

**, ...

派生类型的底层 就是 基本类型。

31.5:指针类型分析方法【派生类型分析】

不论是声明一个多级指针

还是 声明一个多级指针为一个形参 ;我们都可以用一个方法去分析它的类型;

因为指针类型都是派生出来的,所以如果我们一下分析不清楚当前的指针的类型时,我们可以通过将往上追溯,去分析它的上一个派生类型,有必要的话,可以追溯到他的基本类型去分析;【这对于数组的数组的数组...

特别有效】,但是你要注意:能反映定义变量的类型的只有该变量的上一级派生类型(距离最近的那一级上层派生类型)

比如: func(int

a[5])-->func(int (*a))

func(int

a[5][2])-->func(int (*a)[2])【int a[5][2] 的上一层派生类型就是int

(*)[2],所以a是一个指向数组的指针,因为这里就是二维,且能反映变量类型的也就是该变量的上一层派生类型,所以这里直接就可以知道了】

32:指向数组的指针

int (*

pArray)[10];【数组指针】

如:

int array[10];

int (* pArray)[10];

pArray =

&array;【这个赋值是类型匹配的】

pArray =

array;【这个编译器会报出警告,array为数组名,数组名指向数组的第一个元素,所以array的类型是 int * ,

并不是int (* )[10]; 这时如果对指针

进行加减1操作,结果差异是很大的】

在形参中,void func(int array[2][3]);或者 void func(int

array[][3]);

在表示式中数组是会被编译器还原为指针模式的

==> void func( int (* array)[3]);

形参就是一个指向数组的指针。

===========================================

int hoge[3][5]; ==>也可以为int

hoge[][5];==>数组的数组==》判断hoge是什么类型的? 较好的方法就是

将这个声明也变为指针类型==》int

*(hoge)[5]==>声明中的i可以省略【表达式中为得到具体的位置所以加i】,一看就可以知道hoge是指向数组的指针。

int hoge_b[3] ==>int *(hoge_b);

i可以省略【表达式中为得到具体的位置所以加i】,所以这里看出hoge_b是一个指向整形的指针。

hoge + 1 ==> 指向下一个包含5个int整形的数组的起始地址

hoge_b + 1 ==》指向下一个int的的起始地址

表达式中:hoge[i][j] ==>*(*(hoge + i)

+ j)

hoge_b[i] ==>*(hoge_b + i)

33:C中如何解读复杂的声明,三步骤

1:找准关键词KEY

2:在关键词KEY的左右

寻找优先级高的符号【关键符号】,该符号指明了这个关键词的大概类型:指针 或 不是 指针

3: 确定了关键词是什么类型后, 去掉关键词,剩下的部分就是

这个关键词 所对应的对象 的类型,也就是这个关键词的具体类型【具体类型】。

如: int (*

pfunc)(double);

第一步:关键词pfunc

第二步:关键符号 * =>说明这个是指针

第三步:int (*)(double)

=>具体类型为一个函数指针

如int (* pArray)[10];

第一步:关键词pArray

第二步:关键符号 * =>说明这个是指针

第三步:int

(*)[10]; =>具体类型为指向一个包含10个int类型的数组的指针【数组指针】

34:C中是不存在多维数组的!!!

所谓的多维数组,其实就是数组, 他只不过是 数组的数组, 严格讲不能叫多维数组。

35:C中计算类型的大小

SIZEOF运算符

sizeof(xxx)==>sizeof这个关键字,编译器编译的时候,编译器会根据具体的环境给出具体的数值。

sizeof(

int (* [5])(double) ) ==> 20

36:表达式

C中何为表达式?

基本表达式:标识符【变量名、函数名】、常量【包括整形常量与浮点常量等】、字符串常量、使用()括起来的表达式。

且所有的表达式都是有类型的。

36:针对表达式,使用sizeof==>sizeof的使用

sizeof有两种使用方法:

1:sizeof(类型名)

2:sizeof 表达式 ==》返回表达式的类型的大小

sizeof的典型用法就是

获取数组的长度

如:char *

color_name[] = {

"black",

"blue",

...

}

那么数组的长度为 sizeof(color_name) / sizeof(char *)

sizeof

其实是不用括号的,如果他的后面跟着的是一个很长的表达式,就返回这个表达式类型的长度。

37:左值 与

右值

什么是左值? 什么是右值?

上面说了表达式的基本类型, 表达式如果能代表某处内存区域的时候,他就是左值,可以被赋值;

当表达式只是代表值的时候,他就是右值。

38:运算符的优先级

C语言中有数量众多的运算符,其中优先级分为15个级别。

针对*p++,这种编码,如何去分析这个结合规则:

有两种解释:

1:* 与 ++ 的优先级 一样,但是* 与

++运算符的结合方向是从右向左,所以是 *(p++)【这种解释来自 K&R】

2:后置++ 比

前置++、*等的优先级都要高,()与[]的优先级是一样的,所以是*(p++)【这种解释来自BNF规则】

39:const修饰符的用法解释

被const修饰后,表达式的类型变为只读,

“只读”并不是说要他这个变量放到FLASH、ROM中或者是用通过CPU设置内存区域的读写规则去保护这个变量不被修改【写】,其实这个修饰符只是编译器在编译时看到这个修饰符,就会对代码做检查,如有违规的代码,编译就报错【const针对的是编译器】。

const最重要的就是搞清楚const的作用范围 即 作用对象;

被cosnt作用的对象 的值不能被赋值,如果查到这样的代码,编译器报错;

const char * src;

==>作用范围*src【红色部分】==》不允许修改:*

src = ‘a’;

char const *src;

char * const src;

==》不允许:src = NULL; 但是*src = 'a'是可以的,const没有对它进行限制。

const char * const

src; ==> 第一个const的作用范围

*src

==> 第二个const的作用范围 src

在声明中,const修改的范围是

忽视掉类型关键词后的右边整个部分。被修饰后的这部分是不能在代码中被赋值的。

40:const修饰的某个类型

不能直接赋值没有用const修饰的同一类型

修char const *name 不能直接赋给

char * 类型的变量,因为如果能够直接这样赋值,那么const的修饰就不起作用了;

必须经过强制转换,char const *

才行赋给char *。

其他类型道理是一样的。

这样的问题经常出现在调用函数时,实参与形参之间。

41:typedef

的使用

用typedef为某个类型定义别名的时候,方法是这样的;

想要为某个类型定义“别名”,就先用这个类型 声明一个

“别名”的变量,然后在这个声明前加上typedef 就OK了;【分析时,直接分析 去掉typedef后,定义的这个变量是什么类型,

就可以分析出用这个别名定义的变量的类型了】

如 unsigned int

=>预用别名 DWORD

就可以这样:

1:  unsigned int DWORD;

2:  typedef unsigned int

DWORD;

OK了;

再如: char hoge[10];

=> typedef char hoge[10];【hoge是指向数组(包含10个char)的指针】 ==> hoge

a; =>指向数组(包含10个char)的指针;

hoge

a[10]; ==>a[10]是一个 包含 10个 指向数组(包含10个char)的指针 的 数组;

typedef char *

String;=>String 是char * 的 别名【String 是指向char 的指针】,那么String类型也就是

指向char的指针类型

String a; a 就是一个指向char的指针变量;

42:再回顾下 表达式中

数组会被转换为指针形式

即便形参这里

定义数组的大小也是被忽略的,因为都会被转换为指针的形式。

void

func(int a[]) 或 void  func(int

a[5]) 都转换为 void  func(int *p)

{

{  {

...  ...

...

}

}  }

====================

【指向数组的指针】

即便形参这里

定义数组的大小也是被忽略的,因为都会被转换为指针的形式。

void

func(int a[][5]) 或 void

func(int a[2][5]) 都转换为 void

func(int (*p)[5])

{

{  {

...  ...

...

}

}  }

数组会被转换为指针的形式,有两种情况

1:作为函数的形参时

2: 使用数组中的元素时

43:声明与定义的区别

定义是声明的更高的一个层次,定义是

带了存储空间分配的声明;声明仅仅是引入一个符号的使用。

如定义一个变量int a; 声明一个变量extern int a;

定义的这个变量,也实现了声明所要实现的功能。

44:字符串常量

char * str;

str = "abc";

实际是"abc"做为常量存放在代码区域,这时再将这个存储地址赋给str 变量。

注意下面的情景:

char str[]  = "abc";

str[0] = 'd';

因为定义的数组在可写空间,所以可以被赋值,该数组初始化时,编译器会生产一堆拷贝代码,将代码区中存放的"abc"拷贝到可写的内存空间去;

char *str

= "abc";

str[0] =

'd';

这是不允许的,因为这个赋值直接写到了 不可写的存储空间中;

sizeof("abcdess"); ==>

得到是"abc"这个字符数组的大小,而不是这个字符数组存放的地址所占空间的大小(指针的大小)

45:函数的指针

即使对“指向函数的指针”

使用*运算符,进行解引用,此时*不没有作用的;

(*******printf)("hello

world") 与 printf("hello

world") 一样的执行。

46:

函数指针一些复杂的声明

int atexit(void

(*func)(void));==>当程序正常结束的时候,通过这个函数,可以回调一个指定的函数。

void

(* signal(int sig, void (*func)(int)))(int)==>signal函数的返回值,是一个指向void ()(int) 函数类型的函数指针。

较易理解的方式为:

typedef void (*sig_t)(int);

sig_t

signal(int sig, sig_t func);

第四章:数组与指针的常用方法

47:

数组与指针的基本使用方法有哪些?

1:作为函数返回值

2:在很多代码中,返回值一般都用来指示函数的处理结果状态,这时如果函数还想要对外传出参数的话,就要在形参上动手脚,用一个指针变量作为形参,这时这个形参美其名曰

传出参数【相对有传入参数】,因为我们通过这个指针变量去操作指针变量指向的变量【通过传出参数这个方法,调用该函数可以得到无数个传出参数对应的结果】;

3:将数组作为函数的参数传递

完全数组的形式【a[5]】作为形参[可参考42]是行不通的,因为他都会被转换为指针的形式,所以如果声明一个形参为数组,其实是声明的是一个指针变量。

数组形式的形参因为上面的原因,我们在通过指针传入数组的地址时,我们一般都要同时传入数组的长度。【没有长度的传入,如果你试图在函数中通过sizeof(指针)来获取数组的长度,这是错误的,这只能得到该指针变量的长度】

4:可变长数组

int *

pArray = NULL;

pArray =

malloc(sizeof(int) * n);

48:

Java的数组

Java的数组只能使用内存的堆区域;

其他的语言int a[10];都是分配在栈中,而Java是不能写成上面这样,而应如下:int[]

a = new int[10];

Java的数组是知道自身的长度的。

a.length

Java的数组在堆中,但是却不能改变长度,没有relloc这样的函数。

49: 组合使用

1:可变长数组的数组

char *

array[5];==>数组的每个元素都是一个char * 的指针变量,可指向某个字符串,也可指向字符;

2:可变长数组的可变长数组

最典型的就是 char

* a[x] => char * *a; ==>a = malloc(sizeof(char *) * x )

定义一个char

**,再通过动态分配去动态分配数组的大小;

典型应用,如int

main(int argc, char ** argv) ==> char ** argv

【char * argv[]】

argc 为参数的个数,但是这个参数不是必须的,因为

argv[]最后一个单元【argv[argc] = NULL】

3:通过参数返回指针【传出参数】

要通过参数传出一个内容,就必须要使用指向该内容的指针,所以要传出指针,形参就必须使用指向指针的指针;fun(FILE

*fp, char **line);

4:将多维数组作为函数的参数传递

5:将多维数组作为函数的参数传递

多维数组不存在,其实是数组的数组;

array[3][4];

func(int (*array)[4])

使用时,可以这样 array[i] 指向一个

包含4个int元素的数组;

50:

可变长结构体

多typedef struct

{

int npoints;

Point point[1];

}

Polyline;

于是定义一个 该结构体的指针: Polyline * pPolyline =

NULL;

再用malloc分配空间,将地址赋给pPolyline;

pPolyline =

malloc(sizeof(Polyline) + sizeof(Point) * (npoints - 1));

malloc分配到一块连续的空间, sizeof(Point) * (npoints - 1)

就是做为point数组的后面的数组的可变长部分;

51:从1开始的数组

double hoge_buf[10];

double * hoge = &hoge_buf[-1];

hoge[1]就是从hoge_buf[0]开始的;

第五章

52:设计以及头文件的书写原则

在开发大型应用程序的时候,非常有必要将程序按照单元功能(模块)分开;

写头文件时必须遵循的原则:

1:所有头文件中,必须要有防止重复#include的保护

2:所有头文件只#include自己直接依赖的头文件

写头文件时的要点:

1:公有头文件与私有头文件必须分开

比如在设计某个模块时,该模块保护很多个c文件,每个c文件可能都有自己的私有头文件,同时某个c文件的某个接口又是要被其他c使用的【或者是该模块提供给外界的接口】,就需要公私分开【如果是某个c文件私有的接口,可以直接放到该c文件的最开始就好了,或者用个私有头文件最好,比如xxxxPri.h】

53:相关数据结构的图示

第六章

54:结构体定义

1:省略结构体的标签

typedef

struct [结构体的标签]{

int a;

int b;

}

sData;

2:不省略结构体的标签

typedef

structsData_g{

int a;

int b;

structsData_g  * pData;

}

sData;

该情况下是不能省略结构体的标签的,因为在结构体内部需要定义指向结构体的指针,这时因为结构体的定义(typedef)还没有结束,不能使用sData,所以只能用标签sData_g

55:结构体之间可以直接赋值

sData a;

sData b;

b = a

;

征服c指针_[转载]《征服C指针》相关推荐

  1. 二维数组的传参【01_数组指针_指向数组的指针】

    2021-04-08 二维数组的传参[01_数组指针_指向数组的指针] #include <stdio.h> #include <stdint.h> #include < ...

  2. c++ 函数指针_进化论——从函数指针到被结构封装的函数指针及参数的应用举例...

    ↑↑↑ 点击上方公众号名称关注,不放过任何转变的机会. ✎ 编 者 悟 语 借口再小也会瓦解人的意志. 文 章 导 读 今天带大家用下函数指针,然后将函数指针和函数参数封装到结构体中,接着将数据用动态 ...

  3. c语言 指针_初识C语言指针

    一切皆为地址 C语言用变量来存储数据,用函数来定义一段功能代码,它们最终都要放到内存中供 CPU 调用. 数据和代码都以二进制的形式存储在内存中,计算机无法从格式上区分某块内存存储的是数据还是代码.当 ...

  4. 征服c指针_征服C指针(C程序员必读经典 原版畅销11年)

    本书的目标与结构--引言 0.1本书的目标 在C语言的学习中,指针的运用被认为是最大的难关. 关于指针的学习,我们经常听到下面这样的建议: "如果理解了计算机的内存和地址等概念,指针什么的就 ...

  5. c语言中较常见的由内存分配引起的错误_内存越界_内存未初始化_内存太小_结构体隐含指针...

    1.指针没有指向一块合法的内存 定义了指针变量,但是没有为指针分配内存,即指针没有指向一块合法的内浅显的例子就不举了,这里举几个比较隐蔽的例子. 1.1结构体成员指针未初始化 1 2 3 4 5 6 ...

  6. c语言数组数据用指针查找,c语言数组与指针_指针篇_2011.ppt

    c语言数组与指针_指针篇_2011 指 针 6.2 指针的概念6.3 指针与数组6.4 字符串的指针6.5 指针数组和指向指针的指针;6.2.1 地址与指针的概念 ;指针的概念;内存地址;2.数组与地 ...

  7. 零基础逆向工程24_C++_01_类_this指针_继承本质_多层继承

    1 类内的成员函数和普通函数的对比 1.1 主要是从参数传递.压栈顺序.堆栈平衡来总结. 1.参数传递:成员函数多传一个this指针 2.压栈顺序:成员函数会将this指针压栈,在函数调用取出 3.堆 ...

  8. 共享内存中使用指针_详解c++中字符指针数组的使用

    之前有写过一篇叫c++中动态数组的使用,今来看看c++中的字符指针数组的使用. 涛哥:c++中的动态数组使用​zhuanlan.zhihu.com 指针数组,就指向指针的指针,很早以前在说指针的时候说 ...

  9. c语言中较常见的由内存分配引起的错误_内存越界_内存未初始化_内存太小_结构体隐含指针

    本篇是基于云天之巅博主音视频开发中的一个bug,继而查阅了的一点资料:本篇转载自博客园某博主的随笔,并做极少量的修改,原文地址:https://www.cnblogs.com/haore147/p/3 ...

最新文章

  1. FT报源检测到目标无法恢复解决过程
  2. C Operator | and can also operate bool operands
  3. MySQL_前缀索引_建立
  4. HYSBZ/BZOJ 1038 [ZJOI2008] 瞭望塔 - 计算几何
  5. 如何开启jvm日志_做了10个小实验:搞懂了JVM三大参数类型
  6. Netscreen204防火墙配置网络设备的SNMP及Syslog配置
  7. MFC 教程【5_MFC对象的创建】
  8. 【译】成为明星数据科学家的13大技能
  9. ORB-SLAM3 yaml文件介绍
  10. ArcScene:构建三维地图
  11. CRM——销售与客户
  12. springcloud微服务视频教程
  13. 干货~~牛人教你如何写好一篇高分SCI论文
  14. GitHub仓库实现CICD快乐的总结嘤嘤嘤
  15. html输入日期算出星座,如何通过日期计算星座
  16. 隧道测量快速坐标反程序48004850计算器
  17. Android直播软件搭建左滑右滑清屏控件
  18. PS初体验:熟悉快捷键
  19. ajax--异步请求
  20. 计算机网络 ping中ttl,ping命令显示的TTL是什么意思?

热门文章

  1. MVC项目中数据的分离
  2. dns服务器架构实验
  3. 【单片机学习之旅】(1-1)单片机概述
  4. 2009年CRM软件年度分析
  5. 宏基因组数据分析专题之展望与数据质控
  6. 苹果6访问限制密码4位_破解6位密码只需4秒!教你3步设置强密码挡住黑客
  7. 下拉菜单的两种实现方式
  8. 从交叉熵角度理解困惑度(perplexity)
  9. mapengpeng1999@163.com MYSQL基础、分页查询
  10. D. Boboniu Chats with Du (664 div2 贪心 枚举)