🍋
type
status
date
slug
summary
tags
category
icon
password
Property
在ANSI C标准之前,声明函数的方案有缺陷,因为只需要声明函数的类型,不用声明任何参数。如果使用的参数个数不对或类型不匹配,编译器根本不会察觉出来。
针对参数不匹配的问题,ANSI C标准要求在函数声明时还要声明变量的类型,即使用函数原型来声明函数的返回类型、参数的数量和每个参数的类型。未标明
imax()
函数有两个 int 类型的参数,可以使用下面两种函数原型来声明:有了这些信息,编译器可以检查函数调用是否与函数原型匹配。参数的数量是否正确?参数的类型是否匹配?
🍋
type
status
date
slug
summary
tags
category
icon
password
Property
C允许函数调用它自己,这种调用过程称为递归。
可以假设有一条函数调用链——
fun1()
调用fun2()
、fun2()
调用fun3()
、fun3()
调用fun4()
。当 fun4()
结束时,控制传回fun3()
;当fun3()
结束时,控制传回 fun2()
;当fun2()
结束时,控制传回fun1()
。递归的情况与此类似,只不过fun1()
、fun2()
、fun3()
和fun4()
都是相同的函数。尾递归
最简单的递归形式是把递归调用置于函数的末尾,即正好在 return 语句之前。这种形式的递归被称为尾递归(tail recursion),因为递归调用在函数的末尾。尾递归是最简单的递归形式,因为它相当于循环。
递归在处理倒序时非常方便(在解决这类问题中,递归比循环简单)。
编写一个函数,打印一个整数的二进制数。二进制表示法根据 2 的幂来表示数字:
🍋
type
status
date
slug
summary
tags
category
icon
password
Property
使用多个函数最简单的方法是把它们都放在同一个文件中,然后像编译只有一个函数的文件那样编译该文件即可。其他方法因操作系统而异
UNIX
假定在UNIX系统中安装了UNIX C编译器cc(最初的cc已经停用,但是许多UNIX系统都给cc命令起了一个别名用作其他编译器命令,典型的是gcc或clang)。假设file1.c和file2.c是两个内含C函数的文件,下面的命令将编译两个文件并生成一个名为a.out的可执行文件:
另外,还生成两个名为file1.o和file2.o的目标文件。如果后来改动了file1.c,而file2.c不变,可以使用以下命令编译第1个文件,并与第2个文件的目标代码合并:
UNIX系统的make命令可自动管理多文件程序。
注意,OS X的Terminal工具可以打开UNIX命令行环境,但是必须先下载命令行编译器(GCC和Clang)。
Linux
假定Linux系统安装了GNU C编译器GCC。假设file1.c和file2.c是两个内含C函数的文件,下面的命令将编译两个文件并生成名为a.out的可执行文件:
🍋
type
status
date
slug
summary
tags
category
icon
password
Property
指针(pointer)是 C 语言最重要的(有时也是最复杂的)概念之一,用于储存变量的地址。前面使用的
scanf()
函数中就使用地址作为参数。如果主调函数不使用return返回的值,则必须通过地址才能修改主调函数中的值。一元&运算符给出变量的存储地址。如果pooh是变量名,那么&pooh是变量的地址。可以把地址看作是变量在内存中的位置。假设有下面的语句:
假设pooh的存储地址是0B76(PC地址通常用十六进制形式表示)。那么:
实现不同,%p表示地址的方式也不同。然而,许多实现都以十六进制显示地址。
该例的输出说明了什么?
首先,两个pooh的地址不同,两个bah的地址也不同,计算机把它们看成4个独立的变量。
🍌
type
status
date
slug
summary
tags
category
icon
password
Property
目录
数组由数据类型相同的一系列元素组成。需要使用数组时,通过声明数组告诉编译器数组中内含多少元素和这些元素的类型。编译器根据这些信息正确地创建数组。普通变量可以使用的类型,数组元素都可以用。
要访问数组中的元素,通过使用数组下标数(索引)表示数组中的各元素。数组元素的编号从0开始,
candy[0]
表示candy
数组的第1个元素,candy[364]
表示第365个元素,也就是最后一个元素。初始化数组
用以逗号分隔的值列表(用花括号括起来)来初始化数组,各值之间用逗号分隔,在逗号和值之间可以使用空格
🍌
type
status
date
slug
summary
tags
category
icon
password
Property
编写一个处理基本类型的函数时,要选择是传递int类型的值还是传递指向int的指针。通常都是直接传递数值,只有程序需要在函数中改变该数值时,才会传递指针。
对于数组别无选择,必须传递指针,因为这样做效率高。如果一个函数按值传递数组,则必须分配足够的空间来储存原数组的副本,然后把原数组所有的数据拷贝至新的数组中。如果把数组的地址传递给函数,让函数直接处理原数组则效率要高。
传递地址会导致一些问题。C 通常都按值传递数据,因为这样做可以保证数据的完整性。如果函数使用的是原始数据的副本,就不会意外修改原始数据。但是,处理数组的函数通常都需要使用原始数据,因此这样的函数可以修改原数组。有时,这正是我们需要的。例如,下面的函数给数组的每个元素都加上一个相同的值:
因此,调用该函数后,prices数组中的每个元素的值都增加了2.5:
该函数修改了数组中的数据。之所以可以这样做,是因为函数通过指针直接使用了原始数据。
然而,其他函数并不需要修改数据。例如,下面的函数计算数组中所有元素之和,它不用改变数组的数据。但是,由于ar实际上是一个指针,所以编程错误可能会破坏原始数据。例如,下面示例中的ar[i]++会导致数组中每个元素的值都加1:
🍌
type
status
date
slug
summary
tags
category
icon
password
Property
假设有下面的声明:
然后数组名zippo是该数组首元素的地址。在本例中,zippo的首元素是一个内含两个int值的数组,所以zippo是这个内含两个int值的数组的地址。
因为zippo是数组首元素的地址,所以zippo的值和&zippo[0]的值相同。而zippo[0]本身是一个内含两个整数的数组,所以zippo[0]的值和它首元素(一个整数)的地址(即&zippo[0][0]的值)相同。简而言之,zippo[0]是一个占用一个int大小对象的地址,而zippo是一个占用两个int大小对象的地址。由于这个整数和内含两个整数的数组都开始于同一个地址,所以zippo和zippo[0]的值相同。
给指针或地址加1,其值会增加对应类型大小的数值。在这方面,zippo和zippo[0]不同,因为zippo指向的对象占用了两个int大小,而zippo[0]指向的对象只占用一个int大小。因此, zippo + 1和zippo[0] + 1的值不同。
解引用一个指针或在数组名后使用带下标的[]运算符,得到引用对象代表的值。
因为zippo[0]是该数组首元素(zippo[0][0])的地址,所以*(zippo[0])表示储存在zippo[0][0]上的值(一个int类型的值)。与此类似,*zippo代表该数组首元素(zippo[0])的值,但是zippo[0]本身是一个int类型值的地址。该值的地址是&zippo[0][0],所以*zippo就是&zippo[0][0]。对两个表达式应用解引用运算符表明,**zippo与*&zippo[0][0]等价,这相当于zippo[0][0],即一个int类型的值。简而言之,zippo是地址的地址,必须解引用两次才能获得原始值。地址的地址或指针的指针是就是双重间接。
显然,增加数组维数会增加指针的复杂度。
🍌
type
status
date
slug
summary
tags
category
icon
password
Property
假设给带int类型形参的函数传递一个值,要传递int类型的变量,但是也可以传递int类型常量。在C99 标准以前,对于带数组形参的函数,情况不同,可以传递数组,但是没有等价的数组常量。C99新增了复合字面量。
字面量是除符号常量外的常量。例如:81.3是double类型的字面量,'Y'是char类型的字面量,"elephant"是字符串字面量。发布C99标准的委员会认为,如果有代表数组和结构内容的复合字面量,在编程时会更方便。
对于数组,复合字面量类似数组初始化列表,前面是用括号括起来的类型名。下面是一个普通的数组声明:
下面的复合字面量创建了一个和diva数组相同的匿名数组,也有两个int类型的值:
初始化有数组名的数组时可以省略数组大小,复合字面量也可以省略大小,编译器会自动计算数组当前的元素个数:
🍍
type
status
date
slug
summary
tags
category
icon
password
Property
目录
字符串是以空字符(\0)结尾的char类型数组。因此,可以把数组和指针的知识应用于字符串。不过,由于字符串十分常用,所以 C提供了许多专门用于处理字符串的函数。
和
printf()
函数一样,puts()
函数也属于stdio.h
系列的输入/输出函数。但是,与printf()
不同的是,puts()
函数只显示字符串,而且自动在显示的字符串末尾加上换行符。字符串字面量(字符串常量)
用双引号括起来的内容称为字符串字面量,也叫作字符串常量。双引号中的字符和编译器自动加入末尾的\0字符,都作为字符串储存在内存中,所以
"I am a symbolic stringconstant."
是字符串字面量。从ANSI C标准起,如果字符串字面量之间没有间隔,或者用空白字符分隔,C会将其视为串联起来的字符串字面量。