Hello, World!
介绍
C语言是一种通用编程语言,与机器工作密切相关。理解计算机内存如何工作是C语言的一个重要的层面。纵然C被认为是“难学的”,但实际上C是一种非常简单语言,有很大的可拓展性。
C是一种非常通用的语言,被用来编写很多应用,比如Windows、Python解释器、Git,等等。
C是一种编译型的语言,这意味着为了运行它,编译器就必须处理我们所写的的代码,然后建立一个可执行文件。这个文件可以被执行,然后完成我们想要做的任务。
我们的程序
每个C程序都使用库,以提供执行必要函数的可能。比如,最基础的函数叫做printf
,(用来打印内容到屏幕),就是定义在stdio.h
头文件中的。
为了能够运行printf
命令,我们还必须直接地包含下面这行代码到我们的程序的第一行:
#include <stdio.h>
程序的第二部分是我们实际要写的代码,main
函数提供了运行的入口。
int main()
{
//... our code goes here
printf("Hello, World!");
return 0;
}
int
关键字指明了main
函数将返回一个简单的整数。返回的数字将直接地指明我们的程序是否成功地工作。如果我们想说我们的程序成功地运行,我们将返回一个整数0
,而大于0
的整数意味着程序出现了某种错误。
注意:C语言程序的每一行都必须以分号(;
)结束。
变量和类型
数据类型
C有很多类型的变量,以下是基础:
- 整型 - 可正可负,使用
char,int,short,long,long long
来定义。 - 非负整型 - 只能是正数,使用
unsigned char,unsigned int,unsigned short,unsigned long,unsigned long long
来定义。 - 浮点型 - 实数,使用
float,double
来定义。 - 结构体(后面会阐述),使用
struct
来定义。
不同类型的变量定义了它们的边界,char
类型的范围从-128
到127
,而long
则从-2,147,483,648
到2,147,483,647
,另一些类型在不同计算机上可能会有差别。
注意:C没有boolean
类型。但是可以使用下面的方法定义:
#define BOOL char
#define FALSE 0
#define TRUE 1
C使用字符数组来定义字符串,后面将会阐述。
定义变量
对于数字,我们通常使用类型int
,在计算机里面占据一个word
的大小(4 Bytes
)。
定义变量foo
和bar
,需要使用下列语法:
int foo;
int bar = 1;
变量foo
可以被使用,但我们没有初始化它,我们并不知道它实际上存储的是什么。变量bar
包含了整数1
。
现在,我们可以做一些数学。假设a
,b
,c
,d
和e
是变量,我们在下面使用加减乘除操作,以及将一个新的值给a
:
int a = 0, b = 1, c = 2, d = 3, e = 4;
a = b - c + d * e;
printf("%d", a); /* will print 1-2+3*4 = 11 */
数组
数组是一种特殊的变量,可以包含超过一个相同类型的值,通过索引来访问。数组使用一种直接的语法来定义:
/* defines an array of 10 integers */
int numbers[10];
从数组中访问元素使用同样的语法。注意C中的数组是以0
为第一个索引的,这意味着如果我们定义了 一个大小为10
的数组,数组的下标是0
到9
,numbers[10]
不是一个实际的值。
int numbers[10];
/* populate the array */
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
numbers[3] = 40;
numbers[4] = 50;
numbers[5] = 60;
numbers[6] = 70;
/* print the 7th number from the array, which has an index of 6 */
printf("The 7th number in the array is %d", numbers[6]);
数组只能有一种类型的元素,因为它们在计算机内存是一系列连续存储的值。正是这样,访问数组中的每一个元素都非常的高效。
多维数组
在之前的数组
章节中,我们概述了什么是数组及其工作原理。我们当时着眼的是一维数组,但C可以创建和使用多维数组,这里是声明多维数组的通用形式:
type name[size1][size2]...[sizeN];
比如,这样:
int foo[1][2][3];
或者,这样:
char vowels[1][5] = {
{'a', 'e', 'i', 'o', 'u'}
};
二维数组
多维数组最简单的形式是二维数组。一个二维数组不过是拓展的一维数组而已,一维数组的的每一个元素都是一个列表:
type arrayName[x][y];
type可以是C的任何数据类型(int,long,long long,double,etc
),arrayName
是一个合法的C标识符或者变量。二维数组也可以看成是x
行y
列的表格,一个3行4列的数组可以表示如下:
在这种情况下,数组中的每一个元素都可以用a[i][j]
来表示,其中a
是数组名,i,
j是该元素在数组中的唯一下标。
其实,并不一定要指定数组a
的行数,因为你可以像下面这样定义:
char vowels[][5] = {
{'A', 'E', 'I', 'O', 'U'},
{'a', 'e', 'i', 'o', 'u'}
};
编译器已经知道这是一个二维数组,你只需要指定有多少列!,编译器可能是智能的,它知道有多少个整数、字符、浮点数,无论你用多少维。
初始化二维数组
多维数组可以使用大括号({}
)来初始化每一行。下面是一个3行4列的数组。为了更加简化,你可以不写行数把它留空,编译照样通过。
int a[3][4] = {
{0, 1, 2, 3} , /* initializers for row indexed by 0 */
{4, 5, 6, 7} , /* initializers for row indexed by 1 */
{8, 9, 10, 11} /* initializers for row indexed by 2 */
};
内部的指定每一行的大括号是可选的。下面的初始化与之前的相同:
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
访问二维数组中的元素
二维数组中的元素是通过下标访问的,即,数组的行标和列标。
int val = a[2][3];
上面的语句将会从数组中取出第3行第4列的元素。
条件
作出决策
生活中,我们常常需要做决定。为了作出决定我们权衡我们的选择,在程序中也是这样的。
这里是一个C语言的判断结构。
int target = 10;
if (target == 10) {
printf("Target is equal to 10");
}
if 语句
if
语句允许我们检查表达式的真假(true or false
),然后根据结果执行不同的代码。
使用==
来判断两个变量是否相等,就像第一个例子那样。
不等运算符也可以用在判断表达式中。比如:
int foo = 1;
int bar = 2;
if (foo < bar) {
printf("foo is smaller than bar.");
}
if (foo > bar) {
printf("foo is greater than bar.");
}
当计算的判断表达式为false
时,我们可以使用else
关键字来执行我们的其他代码。
int foo = 1;
int bar = 2;
if (foo < bar) {
printf("foo is smaller than bar.");
} else {
printf("foo is greater than bar.");
}
有时我们会得到超过一个选择,这时,使用多个if else
来完成。
int foo = 1;
int bar = 2;
if (foo < bar) {
printf("foo is smaller than bar.");
} else if (foo == bar) {
printf("foo is equal to bar.");
} else {
printf("foo is greater than bar.");
}
你也可以使用多个嵌套if else
语句如果你喜欢。
int peanuts_eaten = 22;
int peanuts_in_jar = 100;
int max_peanut_limit = 50;
if (peanuts_in_jar > 80) {
if (peanuts_eaten < max_peanut_limit) {
printf("Take as many peanuts as you want!\n");
}
} else {
if (peanuts_eaten > peanuts_in_jar) {
printf("You can't have anymore peanuts!\n");
}
else {
printf("Alright, just one more peanut.\n");
}
}
两个或者多个判断表达式也可以同时计算。判断两个表达式是否都是真(true
),使用&
,判断它们至少有一个为真,使用||
。
int foo = 1;
int bar = 2;
int moo = 3;
if (foo < bar && moo > bar) {
printf("foo is smaller than bar AND moo is larger than bar.");
}
if (foo < bar || moo > bar) {
printf("foo is smaller than bar OR moo is larger than bar.");
}
非操作符可以用来取反:
int target = 9;
if (target != 10) {
printf("Target is not equal to 10");
}
字符串
定义字符串
C中的string
实际上是字符数组。纵然使用指针是高级的话题,后面会阐述,我们将使用指针来定义简单的字符串,以下面的形式:
char * name = "John Smith";
这种方法创建的string
只能读。如果我们希望在字符串上操作,我们需要将它定义局部字符数组:
char name[] = "John Smith";
这种表示法和上面的是有区别的,因为我们分配了一个数组变量所以我们能够操纵它。空的中括号[]
告诉编译器来动态地计算数组的大小。
char name[] = "John Smith";
/* is the same as */
char name[11] = "John Smith";
即使字符串John Smith
正好只有11个字符,但我们还必须在定义的时候加上一个。这是由字符串的表示法决定的。C中字符串的结尾有一个特殊字符\0
表示字符串的结束,但这个字符不会纳入字符串长度的计算中,只是便于编译器处理罢了。
使用printf来格式化字符串
我们使用printf
命令来格式化一些字符串,以下面的形式:
char * name = "John Smith";
int age = 27;
/* prints out 'John Smith is 27 years old.' */
printf("%s is %d years old.\n", name, age);
注意:在打印字符串的时候,我们必须增加\n
来换行,因为printf
函数不是默认换行的。
字符串的长度
函数strlen
传入字符串的地址,返回其长度。
char * name = "Nikhil";
printf("%d\n",strlen(name));
字符串比较
函数strncmp
比较两个字符串,参数是两个字符串,以及要比较的长度;如果他们相等则返回0
,若不同则返回其他数字。也有一种不太安全的版本叫strcmp
,但不推荐使用,比如:
char * name = "John";
if (strncmp(name, "John", 4) == 0) {
printf("Hello, John!\n");
} else {
printf("You are not John. Go away.\n");
}
字符串拼接
函数strcat
在后面增加n
个src
中字符到dest
中,其中,n
是min(n,length(src))
;参数是源字符串src
和目的字符串dest
,以及要增加的最大字符的数量。
char dest[20]="Hello";
char src[20]="World";
strncat(dest,src,3);
printf("%s\n",dest);
strncat(dest,src,20);
printf("%s\n",dest);
For循环
C中的for
循环是直接的。当一段代码需要多次执行时使用循环来解决,for
循环使用一个迭代变量,通常表示为i
。
对for
循环给出如下定义:
- 用初始值初始化迭代变量
- 检查条件,判断是否已经到达最后的值
- 改变迭代变量的值
比如说,我们希望迭代一段代码10次,我们会写:
int i;
for (i = 0; i < 10; i++) {
printf("%d\n", i);
}
注意:在for
循环头部声明迭代变量是C++
的语法(随处声明随处使用)。
这个代码快将打印数字0
到9
(一共10个数字)。
for
循环也可以迭代数组的值。比如,我们想要计算数组内元素的总和,我们使用迭代变量i
作为数组索引:
int array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sum = 0;
int i;
for (i = 0; i < 10; i++) {
sum += array[i];
}
/* sum now contains a[0] + a[1] + ... + a[9] */
printf("Sum of the array is %d\n", sum);
While 循环
while
循环与for
循环很像,但功能更少。但条件为true
时,while
循环会不断地执行内部的代码块。比如说,下面的代码将会正好执行10次。
int n = 0;
while (n < 10) {
n++;
}
while
循环也可以无限地执行,如果条件永远都是true
的话。在C中,所有非0
的值都是true
。
while (1) {
/* do something */
}
循环的控制
有两种循环的控制方法,break
和continue
。
break
将结束整个循环,即使循环没有完全地执行指定的次数。
int n = 0;
while (1) {
n++;
if (n == 10) {
break;
}
}
continue
语句用来跳过当次循环,即,对于一次迭代,continue
以下的语句将不会被执行。下面的例子,continue
导致了某些情况下printf
函数将被忽略,只有偶数才被输出。
int n = 0;
while (n < 10) {
n++;
/* check that n is odd */
if (n % 2 == 1) {
/* go back to the start of the while block */
continue;
}
/* we reach this code only if n is even */
printf("The number %d is even.\n", n);
}
函数
C函数是简单的,但由于C的工作方式,函数的功能有一些限制。
- 函数接收固定或者不定数量的参数
- 函数只能只返回一个值,或不返回任何值。
在C中,实际参数按值的方式拷贝,这意味着我们不能改变在函数外部的变量的值。如果要改变,我们必须使用指针,后面会讲到。
函数以下面的形式定义:
int foo(int bar) {
/* do something */
return bar * 2;
}
int main() {
foo(1);
}
我们定义的函数foo
接收一个整型参数bar
,将其乘以2,然后返回。
调用函数foo
采用以下语法,传入参数bar
的值是1。
foo(1);
在C中,函数必须先定义再使用。函数可以先在文件开始处或者头文件中声明,然后再实现;也可以按照使用的顺序实现。
The correct way to use functions is as follows:
使用函数的正确方法如下:
/* function declaration */
int foo(int bar);
int main() {
/* calling foo from main */
printf("The value of foo is %d", foo(1));
}
int foo(int bar) {
return bar + 1;
}
我们也可以创建一个没有返回值的函数,使用关键字void
:
void moo() {
/* do something and don't return a value */
}
int main() {
moo();
}
Static
static
是C语言关键字,可以用来修饰变量和函数。
什么是静态变量?
默认地,变量在定义的时候作用域都是局部的。将变量定义为static
可以将其作用域提升到整个文件;结果,这些static
变量可以在文件内部访问。
考虑下面的程序,我们想计算所参与的跑步者。
#include<stdio.h>
int runner() {
int count = 0;
count++;
return count;
}
int main()
{
printf("%d ", runner());
printf("%d ", runner());
return 0;
}
我们将会看到count
并没有被更新,因为当函数完成的时候,变量count
会从内存中移除。但是,当static
变量使用时,情况将会不一样,整个文件使用同一个count
,更改也会被任何函数发现。
#include <stdio.h>
int runner()
{
static int count = 0;
count++;
return count;
}
int main()
{
printf("%d ", runner());
printf("%d ", runner());
return 0;
}
什么是静态函数?
在C中,函数默认都是全局的。如果我们将函数定义为static
,它的作用域将降低到包含该函数的文件。
语法就像这样:
static void fun(void) {
printf("I am a static function.");
}
静态 VS 全局
静态变量只在包含它的文件内部有效,而全局的变量可以在文件外部访问。