🍋 ANSI C函数原型
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()都是相同的函数。
notion image
 

尾递归

最简单的递归形式是把递归调用置于函数的末尾,即正好在 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
 
有时需要在一个函数中更改其他函数的变量。例如,普通的排序任务中交换两个变量的值。假设要交换两个变量x和y的值。简单的思路是:
这完全不起作用,因为执行到第2行时,x的原始值已经被y的原始值替换了。因此,要多写一行储存x的原始值:
 
两个变量的值并未交换!
interchange()没有问题,它交换了 u 和 v 的值。问题出在把结果传回 main()时。interchange()使用的变量并不是main()中的变量。因此,交换u和v的值对x和y的值没有影响!
 
 
🍌 数组
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
 
🍌
目录

 
什么是指针?
从根本上看,指针(pointer)是一个值为内存地址的变量(或数据对象)。正如char类型变量的值是字符,int类型变量的值是整数,指针变量的值是地址
假设一个指针变量名是ptr,可以编写如下语句:
对于这条语句,我们说ptr“指向”pooh。ptr&pooh的区别是ptr是变量,而&pooh是常量。或者,ptr是可修改的左值,而&pooh是右值。还可以把ptr指向别处:
🍌 const
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."是字符串字面量。
🍍 字符串输入输出
type
status
date
slug
summary
tags
category
icon
password
Property
 
🍍

 
 
如果想把一个字符串读入程序,首先必须预留储存该字符串的空间,然后用输入函数获取该字符串

分配空间

不要指望计算机在读取字符串时顺便计算它的长度,然后再分配空间(计算机不会这样做,除非你编写一个处理这些任务的函数)。假设编写了如下代码:
虽然可能会通过编译(编译器很可能给出警告),但是在读入name时,name可能会擦写掉程序中的数据或代码,从而导致程序异常中止。因为scanf()要把信息拷贝至参数指定的地址上,而此时该参数是个未初始化的指针,name可能会指向任何地方。