C语言知识列表

C语言的诞生时间:没有Unix早,是为了重写Unix(最开始用汇编语言编写)而诞生的,Unix是C语言的根基。
C语言的创始人:Thompson和Dennis 大约在1970年,第一个C语言编译器也是在这时出现的。
C语言的类型系统:最初是为了帮助C编译器设计者区分机器所拥有的不同数据类型,如单精度浮点数,双精度浮点数和字符等,所以C语言本质上排斥强类型,允许程序员在必要的时候可以在不同类型的对象间赋值,C语言的其他一些特性也是为C编译器设计者而建立的,因为在最初的几年C语言的主要客户就是那些C编译器设计者!
C语言的数组下标是从0开始而不是从1开始:C专家编程4页
C语言的基本数据类型直接与底层硬件相对应:C专家编程4页
auto关键字显然是摆设:C专家编程4页
表达式中的数字名可以看成是指针:C专家编程4页
float被自动扩展为double: C专家编程4页
不允许嵌套函数: C专家编程5页
register关键字:C专家编程5页
千万不要使用C预处理器来修改语言的基础结构,这样一来C语言就不再是C语言了!
ANSI C对C编译器必须支持的某些特性的最小值的限制:C专家编程14。
一个遵循标准的C编译器必须提供标准的库函数:C专家编程16。
函数原型中声明参数类型:方便编译器在调用的实参和声明时的形参直接进行一致性检查,编译器不会理会参数的名称!
char *可以赋值给const char*,但反过来不对,而char **和const char**不可以相互赋值:C专家编程19。
在const char*中,const限定符是指向指针所指向类型,而不是指针本身!
整型升级和算术转换:C专家编程22。
当一个负数和一个unsigned数比较时,首先编译器会将这个负数转换为无符号数(一个非常大的正数),比较结果不会正确:C专家编程23!
注意:sizeof的返回值是unsigned int类型,所以在比较之前我们要对sizeof的返回值强制转换为int类型!
注意:尽量不要在你的代码中使用无符号类型,尽量使用象int这样的有符号类型:C专家编程24。
应使用sizeof(array)/sizeof(array[0])来求数组长度,而不要使用sizeof(array)/sizeof(元素类型)来求数组长度!
FSF:自由软件基金会(Free Software Foundation)。创始人:stallman.
GNU:GNU is not Unix.
#progma指示符的行为是由编译器任意定义的:C专家编程16。
ANSI C通常称为C89。C89是C++的基础!
malloc(strlen(str))一定是错误的,而malloc(strlen(str)+1)才是正确的。而其他的字符串出处理库函数几乎都包含一个额外的空间,用
于容纳字符串结尾的'/0'字符!
switch语句的fall through特性带来的麻烦,C语言将fall through作为switch缺省模式是一个失误:C专家编程29。
const关键字并不表示常量,只是表示他是readonly的:C专家编程31页例子。
注意:break语句是用于跳出最近的那层循环语句或switch语句:C专家编程34。
C语言中,相邻的字符串常量将被自动组合为一个字符串,这也会引起数组初始化时的问题:C专家编程34。
C语言允许数组初始化时有拖尾巴逗号:C专家编程35。
C语言将函数默认为是全局可见的是一个失误:C专家编程36。
C语言中的符合重载:C专家编程37。
sizeof是一个操作符而不是一个函数,当sizeof的操作数是个类型名时两边必须加上括号,而操作数如果是变量则不必加括号。
i = 1,2;的执行结果i的值为1,这是因为赋值运算符的优先级比逗号运算符高!
C语言运算符优先级存在的问题:C专家编程38。
注意:大多数编程语言并没有明确规定操作数计算的次序:C专家编程40。
对于&&和||操作符,其操作数的计算顺序是规定的,严格按照从左到右的顺序依次计算两个操作数,当结果提前得知时便可忽略剩余的计算。
'/'字符可以用来对一些字符进行转义,包括回车键,被转义的回车键在逻辑上把下一行当作当前行的延续,主要可以用于连续多行的宏定义,延续一个多行的字符串常量,但注意千万不要在'/'和回车键之间有空格,这将是一个很难发现的错误!
x+++1被解析为x++ + 1,这是根据ANSI C的大口策略而得出的!
z = *x/*y是一个错误,当'/'和'*'连在一起时,C编译器会将理解为注释的开始!
在C语言中,自动变量在堆栈中分配内存,当包含自动变量的的函数或代码块退出时,它们所占用的内存便被回收,它们的内容一定会被下一个所调用的函数覆盖,这一切取决于堆栈中先前的自动变量位于何处,活动函数声明了什么变量,写入了什么内容,原先自动变量地址的内容可能被立即覆盖,也可能稍后才被覆盖。
函数返回一个指向字符串常量的指针没有问题,因为字符串常量是在全局数据区分配空间的!
静态数组和全局数组的问题是:大型缓冲区如果闲置不用是非常浪费空间的!
如果程序员可以在同一代码块中同时进行"malloc"和"free"操作,这样的内存管理是最为轻松的!
一个希望接受三个参数的函数实际上只传递给他一个参数,该函数将从堆栈中再抓两个参数!
int *p[10],p是一个int类型的指针数组!
int (*p)[10],p是一个指向数组的指针,数组内有10个int类型的元素!
const int *p,表示p所指向的对象是只读的!
int const *p,表示p所指向的对象是只读的!
int * const p,表示p指针本身是只读的!
const int * const p,表示p指针本身和p所指向的对象都是只读的!
int const * const p,表示p指针本身和p所指向的对象都是只读的!
typedef被称为“存储类型说明符”!
不得不承认:C语言得声明真的是非常晦涩难懂的!
C语言中的存储类型说明符有:extern, static, register!
C语言中的类型限定符有:const, volatile!
C语言中的类型说明符有:void, char, short,int, long, unsigned char, unsigned short, unsigned int, unsigned long, float, double, struct, union, enum!
C语言中foo()()这样的语句是非法的,因为函数的返回值不能是一个函数,但函数的返回值允许是一个函数指针,如(*fun())()!
C语言中foo()[]这样的语句是非法的,因为函数的返回值不能是一个数组,但函数的返回值允许是一个数组指针,如(*fun())[]!
C语言中foo[]()这样的语句是非法的,因为数组里不能有函数, 但数组里允许有函数指针,如(*foo[])()!
int型变量i跟只包含一个int型成员的结构变量s在参数传递时的方式可能完全不同,一个int型参数一般会被传递到寄存器中,而结构参数则很可能被传递到堆栈中。
在结构体中包含一个指向结构本身的指针,这种方法常用于链表,树以及其他许多动态数据结构!
C语言中结构体的声明与定义:C专家编程59。
在C语言中联合中,所有的成员都从偏移地址零开始存储,这样每个成员的位置都重叠在一起,在某一时刻,只有一个成员真正存储于该位置。
联合一般作为大型结构体的一部分存在,一般用来节省空间:C专家编程62。
C是一个弱类型语言!
枚举通过一种简单的途径,把一串名字与一串整型值联系在一起,在C中枚举的所有功能都可以通过#define来完成,但define定义的名字在编译时被丢弃,而枚举名字则通常一直在调试器中可见:C专家编程63。
C语言声明的优先级规则:C专家编程64。
char * const *(*next)()表示next是一个指针,是一个指向函数的指针,该函数的返回值也是一个指针,该指针指向一个类型为char的常量指针:C专家编程63。
typedef是为一种类型引入新的名字,typedef类似于宏文本替换,但它们之间存在一个关键性的区别,有两点:C专家编程68。
用typedef定义一个函数指针类型,型如typedef void(*pFunc)(int)。
signal()是一种系统调用,用于通知运行时系统,当某种特定的“软件中断”发生时,调用特定的程序,在ANSI C中,signal函数的原型为:void (*signal(int sig, void(*func)(int)))(int),signal函数有两个参数一个是int型,一个是函数指针,返回值也是一个函数指针。
在C语言中,每个结构和联合都有自己的名字空间:C专家编程69 3.7。
注意:结构标签和结构类型的区别:它们是不同的东西,我们要通过给标签加上_tag后缀来表示这是一个结构标签:C专家编程70。
不要为了方便起见就对结构使用typedef,这样做的唯一好处就是在定义结构变量的时候不必书写struct关键字,但恰恰是这个关键字向我们提供了一些信息,所以,我们应该在结构变量的定义中使用结构标签,这样做使代码更为清晰。
typedef也可以为后面的强制类型转换提供一个简单的名字。
char *(*c[10])(int **p)表示:c是一个数组,它的元素类型是函数指针,函数的参数是int **,返回值是char*。
在C语言中牢记两个优先级就够了:乘法和除法先于加法和减法,在涉及其它操作符时一律加上括号。
注意:所有优先级相同的操作符它们的结合性也是相同的,这是必须如此的!
赋值符具有右结合性,即赋值表达式从右至左执行,而位运算符具有左结合性,即位运算表达式从左至右执行,即a=b=c,执行后a的值为c。
从流中读入一个字符串要使用库函数fgets,而不要使用gets库函数,gets是不安全的:C专家编程42。
在C语言中,一个对象必须有且只有一个定义,但它可以有多个extern声明,这里的对象只是指跟链接器有关的东西,如函数和变量。
定义是一种特殊的声明,它创建了一个对象,声明简单地说明了在其他地方创建的对象的名字,它允许你使用这个名字。
定义是用来确定对象的类型并为其分配内存,用于创建对象,而声明用于描述对象的类型,用于指代其他地方定义的对象。
用extern声明的数组并不需要提供数组长度,这是因为在声明时并不会为数组分配内存。
在声明多维数组时,需要提供除最左边一维之外的其他维的长度,这就给编译器足够的信息以产生相应的代码。
在C语言中左值在编译时可知,它表示存储结果的地址,而右值直到运行时才知道,右值表示变量的内容。
数组名是个左值,但是它是一个不可修改的左值,不可以对其进行赋值。
编译器为每个变量分配一个地址,这个地址在编译时可知,而且该变量在运行时一直保存于这个地址,相反,存储于变量中的值只有在允许时才可知,如果需要用到变量中的值,编译器就发出指令从指定地址读入变量值并将其存于寄存器中。
在C语言中,每个符合的地址在编译时可知,如果编译器需要一个地址(可能需要增加偏移量)来执行某种操作,它就可以直接进行操作,而不需要增加指令来首先取得具体的地址,相反,对于指针,必须首先在运行时取得它的当前值,然后才能对它进行解除引用操作,指针的操作很灵活,但需要增加一次额外的提取。
int *a,a可以指向任何一个int型变量或一个int型的数组。
int a[100],a数组的地址并不能改变,在不同的时候它的内容可以不同,但他总是表示100个连续的内存空间。
指针与数组的区别:C专家编程86。
数组和指针都可以在它们的定义中用字符串常量进行初始化。
通常在定义指针时,编译器只分配指针本身的空间,而不用为指针所指向的对象分配空间。
在ANSI C中初始化指针时所创建的字符串常量被定义为只读的。如果试图通过指针修改这个字符串的值,程序会出现未定义的行为。通常字符串常量被存储在只读的文本段中,以防止它被修改。
由字符串常量初始化的数组是可以修改的。
编译器创建一个输出文件,这个文件包含了可重定位的对象,这些对象就是与源程序对应的数据和机器指令。
静态链接与动态链接的区别:C专家编程93。
即使是在静态链接中,整个库文件也并没有被完全装到可执行文件中,所装入的只是需要的函数。
ABI,应用程序二进制接口:C专家编程94。
注意:动态链接是一种just int time(JIT)链接,这表示程序在运行时必须能够找到它们所需要的库函数,链接器通过把库文件名和路径名植入可执行文件中来做到这一点,这就要求函数库的路径不能随意移动,除非在链接器中进行了特殊的说明,否则当程序调用该函数库的函数时,就会在运行时导致失败。执行程序的机器必须要有该程序链接所需要的函数库,而且这些函数库必须位于在链接器中所说明的目录。
任何人都都可以创建静态函数库或动态函数库,只需要编译一些不包含main函数的代码,并将编译所生成的.o文件用正确的实用工具进行处理如果实静态库使用ar,如果实动态库使用ld。
生产和使用静态库和动态库:C专家编程96。
注意:出于安全性,性能和创建/运行独立性方面的考虑,在链接函数库的时候已经不提倡使用环境变量了,一般还是在链接的时候使用-Lpathname和-Rpathname选项。
怎么才能知道必须要链接那些函数库呢,观察头文件(即观察include指令)来确认所使用的函数库,程序中的每个头文件都可能代表一个必须链接的库。
注意:始终要将-l函数库选项放在编译命令行的最右侧。
Interposition是指通过编写与库函数同名的函数来取代该库函数的行为,它可以使库函数在特定的程序中被同名的用户函数所取代,这通常是用于调试和提高效率,但最好不要使用:C专家编程102。
注意:最好不用让程序中的任何符合成为全局的,除非有意把它们作为程序的接口之一。
ld程序的有用选项:C专家编程107。
链接器工作的三个阶段:链接-编辑(link-editing),载入(loading)和运行时链接(runtime linking)。静态链接的模块被链接编辑并载入以便运行;动态链接的模块被链接编辑之后载入,并在运行时进行链接以便运行。
缺省的输出文件a.out是assembler output(汇编程序输出)的缩写形式,准确的说应该是汇编程序和链接编辑输出文件,即它其实不是汇编程序输出而是链接器输出。
ELF是指Extersible Linker Format(可扩展链接器模式),现在又指Executable and Linker Format(可执行文件和链接格式)。
目标文件和可执行文件可以又几种不同的格式:C专家编程117。
在UNIX中段表示一个二进制文件相关的内容块,有文本段,数据段和bss段。
在X86的内存模型中,“段”表示一种设计的结果,在这种设计中地址空间并非一个整体,而是分成一些64K大小的区域称为“段”。
各种C语句会出现在可执行文件哪些段中:C专家编程118。
局部变量不会进入到可执行文件中,它们是在运行是创建的:C专家编程118。
bss段中,bss是Better Save Space的缩写,即更有效的节省空间,BSS段只保存没有值的变量,所以实际上它并不需要保存这些变量的映射,运行时需要的BSS段的大小记录在目标文件中,但是BSS段(不像其他段)并不占据目标文件的任何空间。
注意:BSS段不保存在目标文件中(除了记录BSS段在运行时所需要的大小)。
文本段是最容易受优化措施影响的段。
目标文件的大小受调试状态下编译的影响,但段不受影响。
为什么目标文件和可执行文件要以段的形式来组织:C专家编程119。
可执行文件中的段在内存中如何布局:C专家编程119。
考虑共享库时的进程地址空间图:C专家编程121。
运行时数据结构包括:堆栈,活动记录,数据和堆:C专家编程121。
堆栈:C专家编程122。
alloca()函数所分配的内存就位于堆栈中,如果想让内存在函数调用结束之后仍然有效,就不要使用alloca分配(它会被下一个函数调用所覆盖)。
活动记录:C专家编程123。
我们不能从函数中返回一个指向函数内局部自动变量的指针,因为这时这个指针已经是一个“悬垂指针”了;C专家编程126。
auto关键字只能用于函数内部,而在函数内部数据的缺省分配方式就是这种。
过程活动记录可能并不位于堆栈中,尽可能地把过程活动记录的内容放到寄存器中会使函数调用的速度更快,而有些语言的编译器会将过程活动记录以链的形式在堆中进行分配。
用于递归过程的过程活动记录也可以在堆中分配,但这样性能比较差,因为在通常情况下,从堆栈中获取内存的速度要更快一些。
支持多个线程就是要为每个线程分配不同的堆栈。
注意:goto语言不可以跳出C语言当前的函数。
setjum和longjum:C专家编程128。
在UNIX系统中,当进程需要更多空间时,堆栈可能会自动增长:C专家编程130。
可以把汇编代码嵌入到C代码中,这通常只用于深入操作系统核心非常依赖机器的任务:C专家编程136。
内存的速度与成本关系图:C专家编程146。
虚拟内存基础知识:C专家编程147。
注意:磁盘的交换区的大小一般是物理内存的几倍,只有用户进程才能换进换出:147。
虚拟内存现在已经成为一项操作系统中不可或缺的技术,它允许多个进程运行于较小的物理内存中。
对于编写应用软件的程序员而言:Cache和虚拟内存都是透明的。
堆区域用于动态动态分配的存储,并通过指针访问。堆中的所有对象都是匿名的,不能按名字直接访问,只能通过指针间接访问,从堆中获取内存的唯一方法就是通过调用malloc(以及同类的calloc, realloc等)库函数。
calloc函数与malloc函数类似,但它在返回指针之前先把分配好的内存全部清零。
ralloc函数改变一个指针所指向的内存区域的大小,即可以扩大也可以缩小,它也经常把内存拷贝到别的地方然后将指向新地址的指针返回给你。
数据段和堆的关系:数据段包含堆,堆也是数据段的一部分,堆实现了数据段可以根据需要自动增长。
有些程序并不需要管理它们的动态内存的使用,当需要内存时,它们简单的通过分配来获得,从来不用担心如果释放它们,这类程序包括编译器和其他运行一段固定(或有限)时间然后终止的程序。当这类程序终止时,所有内存会被自动回收,细心检查哪块内存需要被回收纯属浪费时间。
运行时间长一点的程序就需要管理动态内存的分配和回收了。
alloca也可以实现动态分配内存,只不过它不是在堆上分配而是在堆栈上分配内存。
内存泄漏的主要可见症状就是罪魁进程的速度会减慢,原因是体积大的进程更有可能被系统换出,让别的进程运行,而大的进程的换进换出时花费的时间也更多。
泄漏的内存往往比忘记释放的数据结构要大,因为malloc所分配的内存通常会对齐为下一个大于申请数量的2的整数次幂(如申请212B,实际上会用掉256B)。
进程的大小通常是指进程本身以及进程运行时所申请的空间大小。
对齐的意思就是数据项只能存储在地址是数据项大小的整数倍的内存位置上。
如果指针应用了一个并不位于你的地址空间中的地址便会引起一个段错误,如解除引用一个未初始化的或非法值的指针,如int *p=NULL;
*p = 5。
导致段错误的几个直接原因:C专家编程161页。
在遍历链表时正确释放元素的方法是使用临时变量存储下一个元素的地址,这样就可以安全地在任何时候释放当前元素,不必担心在取下一个元素的地址时还要引用它:C专家编程162。
当程序出现坏指针时,什么样的结果都有可能发生,如果你运气好,指针指向你的地址空间之外,这样你在第一次使用该指针时就会出错终止,如果你不走运,指针指向你的地址空间之内,并损坏它所指向的内存的任何信息。
注意:参数传递也是一个隐式类型转换发送的地方。
在参数传递的过程中如果没有原型声明,则会发生隐式类型转换,在被调用函数内部再根据函数定义是参数的声明类型进行相应的裁减,如果我们有了函数原型,缺省的参数类型提升就不会发生,如果参数声明为char,实际所传递的也是char。
隐式类型转换的原因:C专家编程174。
注意:ANSI C没有定义一个标准的函数来获取一次按键后的字符,所有的库函数(包括getchar)都必须等待用户按下回车键表示行输出结束后才能得到输入的数据。
在UNIX中实现逐字节的输入:C专家编程180。
调用库函数之后检查errno:C专家编程181。
void (*state[MAX])(int),这种形式是声明了一个函数指针数组,state是一个函数指针数组,它有MAX+1个元素,函数有一个int型参数,函数返回值是void类型。
用C语言实现有限状态机:C专家编程183。
散列表:C专家编程186。
复杂的类型转换的编写步骤:188。
什么时候数组和指针相同:C专家编程200图,201表。
虽然大多数情况下指针和数组可以互换使用,但是指针和数组在编译器处理时是不同的,在运行时的表示形式也是不同的,并可能产生不同的代码,对编译器而言,一个数组就是一个地址,一个指针就是一个地址的地址。
作为函数参数的数组和指针两者是可以完全互换的。
实际上,对数组的引用如a[i]在编译时总是被编译器改写为*(a+i)的形式。
注意:[]表示一个取下标操作符,取下标操作符取一个整数和一个指向类型T的指针,所产生的结构类型是T。
在表达式中数组和指针是可以互换的,因为它们在编译器里的最终形式都是指针。
在表达式中数组不能被指针替代的几种例外情况:C专家编程202也脚注。
编译器自动会将下标的步长调整到数组元素的大小,这也是为什么指针总有类型限制,每个指针只能指向一种类型,因为编译器需要知道进行解除引用操作时应该取几个字节,以及每个下标步长应取几个字节。
C语言把数组下标作为指针的偏移量。
指针比数组更有效率这种说法是不正确的,因为现在的优化器通常将数组转换为更有效率的指针表达形式,并生成相同的机器指令。
在处理一维数组的时候,指针并不见得不数组更快,C语言把数组下标改成指针偏移量的根本原因是指针和偏移量是底层硬件所使用的基本模型。
标准规定作为“类型的数组”的形参的声明应该调整为“类型的指针”。在函数形参定义这个情况下,编译器必须把数组形式改写成指向数组的第一个元素的指针形式,编译器只向函数传递数组的地址,而不是整个数组的拷贝。也就是说void a(int *b),void a(int b[])和void a(int b[200])这几种形式都是相同的。
之所以要把传递给函数的数组参数转换为指针是出于效率的考虑,在C语言中,所有非数组形式的数据实参都以传值方式调用(对实参做一份拷贝并传给调用函数,函数不能修改作为实参的实际变量的值,而只能修改传递给它的那份拷贝)。然而,如果要拷贝整个数组,无论在时间还是在内存空间上的开销都可能是非常大的,而且在绝大多数情况下你实际并不需要拷贝整个数组,你只是想告诉函数在那一时刻对哪个特定的数组感情趣。
类似的,函数的返回值也绝不能是一个函数或数组,而只能是指向数组和函数的指针。
可以理解为,除了数组和函数之外的所有的C语言参数在缺省情况下都是传值调用,数组和函数则是传址调用,数据也可以使用传址调用,只要在它前面加上取地址操作符(&)就可以了。
我们没有办法把数组本身传递给一个函数,因为它总是被自动转换为指向数组的第一个元素的指针,当然在函数内部使用指针所能进行的对数组的操作几乎跟传递原数组本身没有什么差别,只不过,如果想用sizeof(实参)来获取数组的长度,所得的结构不正确而已。
指针和数组可交换性的总结:C专家编程209。
在C语言中,a[i,j,k]是合法的表达形式,只是它不等于a[i][j][k],实际上是a[k],这也是逗号表达式的值。
尽管术语上称为“多维数组”,但C语言实际上只支持“数组的数组”,所以当提前C语言的数组时,就把它看作是一种向量,也就是某种对象的一维数组,数组的元素可以是一个数组。
如何分解多维数组:C专家编程211。
对于int a[1][2][3],a对应的指针表示是int (*p)[2][3],a[i]对应的指针表示是int (*p)[3],a[i][j]对应的指针表示是int *p。
在“数组的数组的数组”中的每一个单独的数组都可以看作是一个指针。
C语言中多维数组最大的用途是存储多个字符串。
一维数组可以通过把初始值都放在一对花括号内来完成初始化,如果在定义数组时未明确它的长度,C语言约定按照初始化值的个数来确定数组的长度。
我们只能在数组声明时对它进行整体的初始化。
多维数组可以通过嵌套的花括号进行初始化。
在初始化数组时,可以在最后一个初始划值的后面加一个逗号,也可以省略它,同时,可以省略最左边下标的长度,编译器会根据初始值的个数推断出它的长度。
如果数组的长度比所提供的初始化值的个数要多,剩余的几个元素会自动设置为0,如果元素的类型是指针,那么它们被初始化为NULL,如果元素的类型为float,那么它们被初始化为0.0。
一种代替“数组的数组”的方法是建立指针数组,字符串常量即可以用来初始化数组的数组,也可以用来初始化指针数组,编译器会自动地把各个字符存储于数组中的地址。注意:也只有字符串常量才可以初始化指针数组。
不使用字符串常量来初始化指针数组的方法:C专家编程215。
多维数组在内存中是以线型的方式排列的。
p[i][j]被编译器解析为*(*(p+i)+j)。
我们可以声明一个一维指针数组,其中每个指针指向一个字符串来取得类似二维字符数组的效果。
对于a[i][j],a可能的声明方式有:int a[2][2],int *a[2],int **a,int (*a)[2]。
数组的数组和指针数组的区别:C专家编程222。
指针数组相对于数组的数组的优势是:存储各行长度不一的表(即锯齿状的字符串数组)以及在一个函数调用中传递一个字符串数组。
创建一个锯齿状数组的方式有两种:共享字符串和拷贝字符串:C专家编程224。
注意:只要有可能尽量不要选择拷贝整个字符串的方法,如果需要从两个不同的数据结构访问它,拷贝一个指针比拷贝整个数组快得多,空间也节省得多。
在参数传递时,数组名被改写为一个指针参数,这个规则并不是递归定义的:C专家编程225。
当我们向函数传递一个一维数组的时候,形参被改写为指向数组第一个元素的指针,所以我们需要一个约定来提示数组的长度,一般有两种方法:1)增加一个额外的参数来表示元素的数目(argc就是起这个作用);2)赋予数组最后一个元素一个特殊的值,提示它是数组的尾部。
(字符串结尾的'/0'字符就是起这个作用)。这个特殊值必须不会作为正常的元素值在数组中出现。
同样,当我们传递一个二维数组的时候也需要指定数组的边界,我们最后放弃传递二维数组,把a[i][j]这样的形式改写为一个一维数组a[i+1],它的元素类型是指向a[j]的指针,在数组最后一个元素a[i+1]里存储一个NULL指针,提示数组结束。
在C语言中向函数传递一个普通的多维数组,而只能传递一个指定维数(除了最左边一维)的多维数组:如func(int a[10][20])尽管这是最简单的方法,但同时也是作用最小的,因为他迫使函数只处理10行20列的int型数组,而通常我们要的是一个更为普通的多维数组参数方法,是函数能够操作任意长度的数组。注意:多维数组最主要的一维的长度(最左边一维)不必显示写明,所有函数都必须知道数组其他
维的确切长度和数组的基地址。有了这些信息,它就可以一次“跳过”一个完整的行,到达下一行。我们也可以声明为如下形式:
func(int a[][20])或func(int (*p)[20])。
即C语言有一个缺陷,就是没有办法向一个函数传递不同长度的多维数组。
对于二维数组的传递我们也可以使用下面的形式:func(char **p)或func(char *a[]),这也就和main函数的argv一样了,注意也只有二维数组才可以这样做,而且必须是指向字符串的指针数组,这是因为字符串和指针都有一个显示的越界值(分别为NUL和NULL),可以作为结束标记。
对于向函数传递数组的总结:C专家编程230。
严格地说,我们无法直接从函数返回一个数组,但是可以让函数返回一个指向任何数据结构的指针,当然也可以是一个指向数组的指针,例如:int (*func())[20]{},这里的func是一个函数,它的返回值是一个指向包含20个int元素的数组的指针。
向printf函数传递一个空指针有时会引起程序的崩溃,因为这是一个未定义的行为,因为C标准规定%s说明符的参数必须是一个指向字符数组的指针,而NULL不是一个这样的指针,所以就会进入到一个未定义的行为。
当预先并不知道数组的长度时,可以使用动态数组,但是在ANSI C中,数组是静态的--数组的长度在编译时便已确定不变,但是我们仍然有办法实现动态数组的功能,它的基本思路就是用malloc()库函数(分配内存)来得到一个指向一大块内存的指针,然后,像引用数组一样来引用这块内存,其机理就是一个数组下标访问可以改写为一个指针加上偏移量,结合realloc使用:C专家编程234。
动态数组对于避免预定义的限制也是很有用的。
数据结构动态增长的另一种方法是使用链表,但是链表不能进行随机访问,你只能线性地访问链表(除非你把频繁访问的链表元素的地址保存在缓存中),而数组则允许随机访问,这可能在性能上造成很大的差别。
strlen计算字符串的长度,它查找字符串中的NULL结束符,它的算法需要遍历正个字符串的内容,因此它的执行效率 是O(n),当字符串很长,而且调用次数很多的时候,执行的速度很慢。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值