2008年5月27日星期二

Cocoa的C语言准备知识


学习C语言

本教程讲解了开始学习Cocoa之前所必需具备的C语言知识。本文的每个章节会使用1~2屏的文字量。不要跳跃着阅读。请认真的研读本文的每个部分。

在开始之前,你必须了解至少一门脚本语言或编程语言,知道什么是函数,变量和循环等名词的含义。学习本文时,你需要在Mac OS X的终端中输入命令。

本教程版权所有Cocoa Dev Central ,作者Scott Stevenson

撰写教程需要大量的时间和精力。如果你觉得本教程有用,请通过捐献支持我们的工作。获得的捐助越多,我们就能有更多的时间来进行写作了。

建议捐款额:有帮助:$5 | 非常有帮助:$12 | 无价:$21


技能测试

在继续阅读本文前,请确信自己能够理解下面的这段代码:

function display_area_code ($code)
{
print ("$code ");
}

$area_codes[0] = 408;
$area_codes[1] = 650;
$area_codes[2] = 510;

/* this is a comment */

$count = 3;

for ($i = 0; $i < $count; $i++)

display_area_code ($area_codes[$i]);
这个例子有一个函数,一条打印语句,一个注释,一个数组,一些变量和一个循环。如果你能从这个例子中区分出这些概念,那么你就可以继续阅读本教程了。

如果你不想自己输入程序代码,你可以下载这个 ,里面包含了所有的例子的源代码。

要编译本文的源码,你需要Mac OS X上的Xcode工具——尽管本文中的大部分代码能够在任何OS上正常编译。

一个简单的C程序

这是你的第一个C程序。把下面的代码复制到任何纯文本编辑器,并保存为名为test1.c的文件。

test1.c

#include <stdio.h>

main ()
{
printf ("I'm a C program\n");
}
#include <studio.h>语句把处理输入输出的C代码包含到了源代码中。我们需要这个语句才能调用printf函数。

下面的一行定义了所有C程序都必须的main函数。

最后,调用了一个printf函数,使得程序执行时能在命令行上显示一些文字。n在文本的最后插入了一个换行符。

使用GCC编译

打开终端,并切换到包含有test1.c文件的目录下。输入命令gcc test1.c -o test1进行编译。要运行程序,输入./test1

如果出现错误,回头检查一下示例代码是不是被正确的粘贴了。
gcc命令的-o选项设置了程序文件的名称。如果你不加上这个参数,程序会自动命名为“a.out”。

关于编译器

你需要使用编译器将C语言的源代码转化成可执行的二进制程序。编译出来的程序通常比脚本运行的快很多。Mac OS X的很大一部分是用C语言写成的。

在本教程中,我们使用命令行中的gcc来编译源代码,但是Xcode提供了这个工具的图形前端。

可移植性

一个C程序通常只能运行在编译它的同类计算机平台上。如果你在Mac OS X上编译了你的C语言源代码,编译出来的程序是无法运行在诸如Linux之类的平台上的,你必须在Linux下重新编译同样的源代码。

在更加复杂的C程序中,你甚至需要为不同的编译平台对源代码做少量的修改,使之能正确编译。这个过程称为移植

C的特色

如果你学习C语言的时候,同你已经掌握的另一门语言比较地学习,那样就会轻松很多。如果你之前学过例如PHP或Perl这样的脚本语言,下面的表格列出了一些对你来说可能比较新鲜的概念。

基本的C概念

编译器
把C源代码转化成可执行程序的工具
变量类型
变量的数据类型
函数类型

d="w0gg20" bgcolor="#cccccc" width="50%">函数返回值的数据类型
头文件(.h)
声明了函数和变量的独立文件
结构体
一组相关的值
枚举类型
一组预定义的值
指针
其他变量的别名

这些都是比较简单的概念。C语言其实并没有那么难,只是因为把所有不同的组件拼装起来,才使C语言看起来那样让人感到迷惑。

如果没有指针,C语言会看起来和PHP几乎完全一样。指针的概念非常诡异,不过Cocoa已经把它抽象出来,是你在进行基础编程的时候无需知道大部分细节。

本教程不会教你如何使用指针。我们会把重点放在让你能够快速起步上。当你写的程序越来越复杂时,可以回头复习指针的内容。

变量类型

在脚本语言中,你可以自由的使用变量。你可以把一个变量的内容从一个整数值变成一个浮点数,甚至是文本。
$variable = 2;
$variable = 1.618;
$variable = 'A';
在C语言中,规则更加严格。你必须声明变量的数据类型,并且变量的类型无法在程序中随意更改。下面是上文中的代码片段的C语言版本:
int variable1 = 2;
float variable2 = 1.618;
char variable3 = 'A';
注意,C语言版的代码中定义了3个不同的变量,每种变量类型一个。不过每个变量只需要定义一次:
float variable2 = 1.618;
variable2 = 3.921;
variable2 = 4.212;

可用的类型

下面是一些你需要知道的C语言内建的变量类型:

类型
描述
例子
int
整数,包括负数
0, 78, -1400
unsigned int
整数(不包括负数)
0, 46, 900
float
浮点数,小数
0.0, 1.618, -1.4
char
单个字符或符号
'a', 'D', '?'

尽管不常见,但是请注意,doublefloat的一种变体,可以保存比float精度更高的浮点数;long则能存储比int类型更大的整数。

C还允许你创建自定义变量类型。

函数类型

在C中,你需要定义函数返回值的类型。函数返回值可以是任何C变量类型,类型定义位于函数名左侧。
int numberOfPeople ()
{
return 3;
}

float dollarsAndCents ()
{
return 10.33;
}

char firstLetter ()
{
return 'A';
}
你还可以指定函数的返回值类型为void。目前,你可以认为这样做是表明函数将不返回任何值:

style="font-family: Courier New;">void printHello ()
{
printf ("Hello\n");
}

参数的类型

传递给函数的参数定义类型也是必须的。与脚本语言不同,你不能为参数指定默认值。
int difference (int value1, int value2)
{
return value1 - value2;
}

float changeDue (float amountPaid, float costOfItem)
{
return amountPaid - costOfItem;
}

函数声明

在C语言中,函数在被调用前必须先声明。你可以把所有的函数都放在main()函数前面,但是这项工作随着代码量的增加而迅速变得繁重不堪。

解决方案是,使用一个函数原型。它看起来像一个函数定义,但是不包含大括号包围的代码块;并且以一个分号结尾:
int difference ( int value1, int value2 );
float changeDue ( float amountPaid, float costOfItem );
在函数原型中,你必须指定返回值的类型,函数名,以及传递给函数的参数的类型。

下面是一个例子,把这段示例代码复制到一个文件中,命名为test2.c
test2.c

#include <stdio.h>

int sum ( int x, int y );

main ()
{
int theSum = sum (10, 11);
printf ( "Sum: %i\n", theSum );
}

int sum ( int x, int y )
{
return x + y;
}
这里我们包含了stdio.h是为了使用printf函数。随后,定义了sum函数的原型。在main函数中,调用sum函数,并把返回值保存到一个名为theSum的变量中。

因为sum函数的返回值是int类型,所以需要把theSum也声明为类int型。变量的类型需要与函数返回值的类型一致

编译示例程序

打开终端,并切换到包含有test2.c文件的目录下。输入命令gcc test2.c -o test2进行编译。

要运行程序,输入./test2

格式化字符串

你可能在想,上面的例子中的%i是什么意思。在一些脚本语言中,例如PHP,你可以自由的在双引号包围的字符串中嵌入变量:
$var1 = 3;
$var2 = 8;
print ("First value: $var1 second value: $var2");
在C语言中,你无法直接在字符串中直接嵌入变量。你必须使用一个格式化字串来作为变量的标记:
int var1 = 3;
int var2
yle="font-family: Courier New;"> = 8;

printf ("First value: %i second value: %i", var1, var2);
格式化字串是由双引号包围的所有内容。你可以在里面任何需要插入变量的位置添加一个带%的格式化标记即可。

跟在百分号后面的字母取决于需要插入的值的类型。在这个例子中,我们使用了%1,因为需要插入的是一个int类型的变量。
格式化标记

int
%i/%d
unsigned int
%u
float
%f
char
%c


格式化字串后面接一个逗号,以及与格式化标记数量一样的由逗号分隔的变量列表。请一定要把变量列表放在闭引号和逗号后面。

Cocoa程序使用NSLog代替printf输出,但是两者的字符串格式化规则几乎是完全一样的。

类型转换

有时你需要把变量转换为另一种类型。例如,你将向函数传入一个float类型的值,但是函数只能接受一个int类型的参数。

有时你可以在一个需要使用float变量的地方使用int变量,而不会有任何错误发生,但是最好是通过手工转换数据类型。这个过程就称为类型转换

int multiply (int x, int y)
{
return x * y;
}

int trips = 6;
float distance = 4.874;
int approxDistance = (int)distance;

int total = multiply ( trips, approxDistance );
正如你在上面的这个例子中看到的那样,类型转换的方法其实就是是在一个值的前面加上用园括号括起来的变量类型名。

类型转换的结果随着源类型和目标类型的不同而不同。把一个float类型的变量转换为一个int类型的变量会直接截去该值小数点之后的部分,类型转换不会进行四舍五入

更多类型转换的例子

这里是一种不需要使用approxDistance变量的函数调用方法。其实就是在函数调用时直接对参数进行类型转换:
int result = multiply (trips, (int)distance);
有时你需要把一个函数的返回值转换为另一种类型,可以使用类似的方法:
int multiply (int x, int y)
{
return x * y;
}

float result;
result = (float) multiply (3, 6);
在函数定义中我们看到,multiply返回值得类型是int。我们需要把这个值保存为float,因此在调用函数的时候,在函数名前面用了一个(float)进行类型转换。返回变量的值将为18.0

头文件

因为C语言中调用函数前必须先声明,因此把相关的函数声明归组到一起将很有帮助,并且易于在同一个地方进行维护。这就是头文件的作用。

头文件在大型的项目中是非常重要的,它是源代码的总览,从而使得我们不需要逐行研究所有的代码。

创建一个头文件

这里是一个头文件的例子。把这个例子复制到一个文件中,保存为math_functions.h。
math_functions.h
int sum (int x, int y);
float average (float x, float y, float z);
下面是函数的实现部分。把下面的代码也复制到一个文件中,保存为math_functions.c。
math_functions.c
int sum (int x, int y)
{
return (x + y);
}

float average (float x, float y, float z)
{
return (x + y + z) / 3;
}
average函数中,被除数的表达式用括号括起来了,否则,只有最后一个变量才进行除法运算。在一个数学表达式中,对不同部分用括号分组是一个良好的编程习惯。

使用头文件

这里是一个使用在上面的头文件中定义的函数的程序示例。复制下面的代码到一个文件中,保存为test3.c。
test3.c

#include <stdio.h>
#include "math_functions.h"
main ()
{
int theSum = sum (8, 12);
float theAverage = average (16.9, 7.86, 3.4);

printf ("the sum is: %i ", theSum);
printf ("and the average is: %f \n", theAverage);
printf ("average casted to an int is: %i \n", (int)theAverage);
}
在标准头文件后面,我们包含了math_functions.h这个头文件。这使得程序能够调用sumaverage函数。

在main函数中,我们调用了sum函数,并把结果保存在一个int变量中。然后又调用了average函数,把结果保存在一个float变量中。

程序中三次使用了printf。一次使用了%i标记指示int变量theSum,一次使用了%f标记指示浮点变量theAverage,还有一次也使用了%i,并把theAverage转换为int类型传递进去了。

stdio.h是放在一对尖括号中的,因为这个头文件来自C函数库(后面会进行介绍)。main_functions.h文件是针对这个程序特殊的,所以包围在一组双引号中。

编译程序示例

现在,你应该有了3个源代码文件,并且都保存在同一个目录中:

math_functions.h —— 数学函数声明
math_functions.c —— 数学函数实现
test3.c —— 实际的程序

转到终端,并切换目录到包含这3个文件的目录中。要编译这个程序,输入“gcc test3.c math_functions.c -o test3”。

要运行程序,输入./test3
这次,我们给了gcc两个输入文件:test3.cmath_functions.c。gcc命令会把两个.c文件的内容合并,转化成一个单个的程序。

不需要在命令中包括math_functions.h。因为在程序中已经使用#include包含了。

结构体

结构体是一组结构化的变量。这里是一个用来存储歌曲信息的结构体的示例。
typedef struct
{
int lengthInSeconds;
int yearRecorded;
} Song;

uote>这个看起来似乎只定义了两个int变量,但是实际上,我们定义了一种新的变量类型。typedef语句为结构体指定了一个名称,在这个例子中,结构体的名称为Song

每个Song类型的变量将能存储2个值:lengthInSecondsyearRecorded。在这个例子中,这两个成员变量都是int类型。不过一个结构体的成员变量可以是任何类型(甚至是另一个结构体)。

一旦你定义了一个结构体,你就可以像使用int, floatchar那样使用它了。你可以定义任意多的Song类型的变量,每个变量拥有自己的长度和年份。

你可以使用点操作符为结构体的成员变量赋值:
Song song1;

song1.lengthInSeconds = 213;
song1.yearRecorded = 1994;

Song song2;
song2.lengthInSeconds = 248;
song2.yearRecorded = 1998;
这里,定义了一个Song类型的变量,名为song1。然后用点操作符设置长度和年份。变量song2也是一个Song类型的变量,但是其成员变量的值是不同的。

函数中的结构体

结构体可以作为函数的输入和输出。函数定义与结构体的定义都可以放到一个头文件中。

把下面的代码复制到一个文件中,保存为song.h
song.h
typedef struct
{
int lengthInSeconds;
int yearRecorded;
} Song;

Song
make_song (int seconds, int year);
void display_song (Song theSong);
在头文件中定义了一个Song结构体。还定义了两个函数make_song和display_song。现在来实现这两个函数:

复制下面的代码到一个文件中,保存为song.c
song.c
#include <stdio.h>
#include "song.h"

Song
make_song (int seconds, int year)
{
Song
newSong;

newSong.lengthInSeconds = seconds;
newSong.yearRecorded = year;
display_song (newSong);

return newSong;
}

void display_song (Song theSong)
{
printf ("the song is %i seconds long ", theSong.lengthInSeconds);
printf ("and was made in %i\n", theSong.yearRecorded);
}
make_song函数需要传入两个int值,返回一个Song结构体。display_song函数可以用任何Song类型的结构体作为输入,并显示其值。注意,make_song在创建一个新Song类型变量的时候会调用display_song函数。

song.c中必须包含song.h,因为函数需要Song类型的定义。你必须在使用到Song类型的所有源代码文件中都包含song.h

结构体的使用

现在,我们需要一个函数来使用song.c和song.h文件。把下面的代码复制到一个新文件中,保存为test4.c。
test4.c
#include <stdio.h>
#include "song.h"

main ()
{
Song
firstSong = make_song (210, 2004);
Song
secondSong = make_song (256, 1992);

Song
thirdSong = { 223, 1997 };
display_song ( thirdSong );

Song
fourthSong = { 199, 2003 };
}
在这个程序中,首先用make_song函数创建了两个Song变量。创建第三个Song变量没有使用make_song函数,而是使用了一种特殊的语法。是将一组用逗号分隔的值用大括号包围后传递给Song变量进行赋值。

这里,我们没有使用make_song函数,因此display_song函数没有自动调用,所以在后面一行中,进行了一次手动调用。而第四个song也是按照同样的方法创建,但是因为一直没有调用display_song,因此它的信息永远不会在终端中显示

相对于直接赋值,使用一个函数来创建新的结构体实例是一个更好选择,这样就能够更加精确的控制整个创建过程了。在这个例子中,我们在创建每个Song变量的同时还能自动地显示其信息。

编译示例程序

现在我们有了song.h,song.c和test4.c这3个文件,我们可以编译它们,看看有什么输出。

转到终端,切换到存有这3个文件的目录,输入命令“gcc test4.c song.c -o test4”。

要运行程序,输入./test4

常量

一个变量的值可以在程序运行时被改变。与之相反,一个常量的值只能在声明时赋值一次,其值无法被修改,直到程序被重新启动。
const float goldenRatio = 1.618;
const int daysInWeek = 7;
任何C变量类型都可以作为常量的类型。唯一需要注意的是,常量必须在创建的时候被赋值。

枚举类型

我们可以在枚举类型上话很多时间讲解,但是考虑到本文的目的是讲解Cocoa相关的知识,因此,这部分将会非常非常的简要。

下面是Cocoa的NSString类中的一个枚举类型,定义了文本搜索选项:
enum {
NSCaseInsensitiveSearch = 1,
NSLiteralSearch = 2,
NSBackwardsSearch = 4,
NSAnchoredSearch = 8,
NSNumericSearch = 64
};
Apple在Cocoa中用枚举类型来把一系列相关的常数归组。这经常被用作为特定的函数设置“状态”。常数的实际值其实并不重要,常数名才是关键。

下面是一个使用上面定义的那个枚举类型的实例。不要担心看不懂这段代码的含义,把注意力集中在枚举类型的使用上:
[string compare:anotherString options:NSCaseInsensitiveSearch];
[string compare:anotherString options:NSLiteralSearch];

函数库

在上面的每个例子中都包含的stdio.h头文件是标准C函数库的一部分。函数库是一个可重用的代码集合。定义了函数,结构体,常量和枚举类型等。

有很多第三方的C函数库可用,有些是免费的,还包含了.c的源代码文件;有些则需要付费,只提供头文件和文档,不提供源代码。
要使用第三方函数库,你需要编译并“连接”到库中。例如,如果你想写一个能够和MySQL交互的C程序,你可以连接到libmysql函数库,在程序中包含它的头文件。

当你使用其他人写的代码的时候,了解使用协议很重要。例如有些许可证要求用户共享程序的所有源代码。

如果你感到很抽象,可以查看一下/usr/include,这里是存放C的头文件的标准位置。

最后的示例

下面讲演演示本教程的最后一个例子,几乎用了上文中所涉及的所有概念。你需要下面几个前文的示例中的源代码文件:

math_functions.h —— 数学函数声明
math_functions.c —— 数学函数实现
song.h —— Song结构体和相关函数的声明、
song.c —— Song函数的实现

把下面的这段示例代码复制到一个文件中,保存为final.c。
final.c
#include <stdio.h>
#include "math_functions.h"
#include "song.h"

main ()
{
const
int numberOfSongs = 3;
printf ("total number of songs will be: %i\n", numberOfSongs);

int i;
for (i = 0; i < numberOfSongs; i++) {
printf ("loop trip %i ", i);
}

printf ("\n");


>Song
song1 = make_song (223, 1998);
Song song2 = make_song (303, 2004);
Song
song3 = { 315, 1992 };

display_song
(song3);

int combinedLength = sum (song1.lengthInSeconds, song2.lengthInSeconds);
printf ("combined length of song1 and song2 is %i\n", combinedLength);

float x = (float) song1.lengthInSeconds;
float y = (float) song2.lengthInSeconds;
float z = (float) song3.lengthInSeconds;

float averageLength;
averageLength = average (x, y, z);

printf ("average length is: %f as a float ", averageLength);
printf ("and %i as an int\n", (int) averageLength);
}
这个例子看起来似乎有点复杂,但是实际上只是在对相同概念的重复。如果这个例子中你还有什么看不懂,请务必回过头阅读前面的相关部分。

编译示例程序

请确保上面提到的4个文件以及final.c都在同一个目录中。转到终端,切换到包含这几个文件的目录。输入命令“gcc final.c song.c math_functions.c -o final”。

要运行程序,输入./final

总结

我们利用很小的篇幅讲解和大量的知识。如果你能理解最后一个示例的代码,你基本上就可以开始学习Objective-C和Cocoa的基础了。

当你有了更多编程经验的时候,你需要深入学习一些高级话题,例如指针和内存管理。下面就是关于这些话题的起点。

Thecacao: C内存和数组
Thecacao: C指针和动态内存

喜欢这个教程?有任何建议?请给我们发送关于本教程的反馈信息

撰写教程需要大量的时间和精力。如果你觉得本教程有用,请通过捐献支持我们的工作。获得的捐助越多,我们就能有更多的时间来进行写作了。

建议捐款额:有帮助:$5 | 非常有帮助:$12 | 无价:$21

Copyright © 2004-2006 Scott Stevenson
TextMate制作

(本翻译用Prism + Google Docs + 人脑制作)

原文地址:http://cocoadevcentral.com/articles/000081.php

版权说明:本文来自Cocoa Dev Central,原文版权在上文已经给出。关于那个捐助信息,我只是照原文翻译,如果你真的有意捐献原文作者,请访问原文,地址在上面也已经给出。因为不知道原文的授权方式,我也懒得联系原作者询问版权事宜,因此本翻译的文字内容暂时按照Public Domain(公有域)方式授权使用,即,本人放弃本翻译的版权。文中的所有图片均来自原文,版权归原作者所有。

免责声明:本人不保证翻译的正确性。本人也不对因使用本翻译造成的直接或间接的损失负责。

(Venj@iLoveMac.cn 译)



4 条评论:

  1. 这个文章是我测试从Google Docs直接发布到Blog的,所以版面有点乱。如果你对本文有兴趣,下面的链接有更好的排版,特别是代码的缩进。
    http://docs.google.com/Doc?docid=ajktsxj4msx2_198wm2xqdnr

    回复删除
  2. v大狠人啊! 8)

    回复删除
  3. test1.c中的
    printf ("I'm a C programn");
    应该是
    printf ("I'm a C program/n");
    还有
    /n在文本的最后插入了一个换行符。
    :)
    非常感谢您的翻译。

    回复删除
  4. [Comment ID #271829 Will Be Quoted Here]
    非常感谢你的建议。这文章是直接从Google Docs发送到Blog里的,所以在格式上有点问题,我在1楼已经给出了Google Docs的原始地址。如有需要,可以去看看。那个反斜杠的问题我在想办法解决。

    回复删除