C语言学习笔记

gcc问题

  1. lm(libm)——链接到数学库

头文件

头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享,分为标准头文件、自定义头文件。

引用头文件的语法

使用预处理指令 #include 可以引用用户和系统头文件。它的形式有以下两种:

1
#include <file>

这种形式用于引用系统头文件。

1
#include "file"

这种形式用于引用用户头文件。

数据类型

在 C 语言中,数据类型指的是用于声明不同类型的变量或函数的一个广泛的系统。变量的类型决定了变量存储占用的空间,以及如何解释存储的位模式。

C语言类型分类

序号 类型与描述
1 基本类型: 它们是算术类型,包括两种类型:整数类型和浮点类型。
2 枚举类型: 它们也是算术类型,被用来定义在程序中只能赋予其一定的离散整数值的变量。
3 void 类型: 类型说明符 void 表明没有可用的值。
4 派生类型: 它们包括:指针类型、数组类型、结构类型、共用体类型和函数类型。

基本类型

整数类型

类型 存储大小 值范围
char 1 字节 -128 到 127 或 0 到 255
unsigned char 1 字节 0 到 255
signed char 1 字节 -128 到 127
int 2 或 4 字节 -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647
unsigned int 2 或 4 字节 0 到 65,535 或 0 到 4,294,967,295
short 2 字节 -32,768 到 32,767
unsigned short 2 字节 0 到 65,535
long 4 字节 -2,147,483,648 到 2,147,483,647
unsigned long 4 字节 0 到 4,294,967,295

浮点类型

类型 存储大小 值范围 精度
float 4 字节 1.2E-38 到 3.4E+38 6 位小数
double 8 字节 2.3E-308 到 1.7E+308 15 位小数
long double 16 字节 3.4E-4932 到 1.1E+4932 19 位小数

void 类型

void 类型指定没有可用的值。它通常用于以下三种情况下:

序号 类型与描述
1 函数返回为空 C 中有各种函数都不返回值,或者您可以说它们返回空。不返回值的函数的返回类型为空。例如 void exit (int status);
2 函数参数为空 C 中有各种函数不接受任何参数。不带参数的函数可以接受一个 void。例如 int rand(void);
3 指针指向 void 类型为 void * 的指针代表对象的地址,而不是类型。例如,内存分配函数 void *malloc( size_t size ); 返回指向 void 的指针,可以转换为任何数据类型。

变量

变量其实只不过是程序可操作的存储区的名称。C 中每个变量都有特定的类型,类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可应用于变量上。

命名规则

由字母、数字和下划线字符组成。它必须以字母或下划线开头,区分大小写。

基本变量类型

类型 描述
char 通常是一个字节(八位), 这是一个整数类型。
int 整型,4 个字节,取值范围 -2147483648 到 2147483647。
float 单精度浮点值。单精度是这样的格式,1位符号,8位指数,23位小数。
double 双精度浮点值。双精度是1位符号,11位指数,52位小数。
void 表示类型的缺失。

C 语言也允许定义各种其他类型的变量,比如枚举、指针、数组、结构、共用体等等变量类型

变量定义

变量定义就是告诉编译器在何处创建变量的存储,以及如何创建变量的存储。变量定义指定一个数据类型,并包含了该类型的一个或多个变量的列表,多个标识符之间用逗号分隔,如下所示:

1
2
3
4
int    i, j, k;    //声明并定义了变量 i、j 和 k,编译器创建类型为 int 的名为 i、j、k 的变量。
char c, ch;
float f, salary;
double d;

变量可以在声明的时候被初始化(指定一个初始值)。初始化器由一个等号,后跟一个常量表达式组成,如下所示:

1
2
3
4
extern int d = 3, f = 5;    // d 和 f 的声明与初始化
int d = 3, f = 5; // 定义并初始化 d 和 f
byte z = 22; // 定义并初始化 z
char x = 'x'; // 变量 x 的值为 'x'

不带初始化的定义:带有静态存储持续时间的变量会被隐式初始化为 NULL(所有字节的值都是 0),其他所有变量的初始值是未定义的。

1
NULL = (void*)0

变量声明

变量声明向编译器保证变量以指定的类型和名称存在,这样编译器在不需要知道变量完整细节的情况下也能继续进一步的编译。变量声明只在编译时有它的意义,在程序连接时编译器需要实际的变量声明。

变量的声明有两种情况:

  • 1、一种是需要建立存储空间的。例如:int a 在声明的时候就已经建立了存储空间。
  • 2、另一种是不需要建立存储空间的,通过使用extern关键字声明变量名而不定义它。 例如:extern int a 其中变量 a 可以在别的文件中定义的。
  • 除非有extern关键字,否则都是变量的定义。
1
2
extern int i; //声明,不是定义
int i; //声明,也是定义

运算符

运算符包括算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符和其他运算符等。

算术运算符

假设变量 A 的值为 10,变量 B 的值为 20,则:

运算符 描述 实例
+ 把两个操作数相加 A + B 将得到 30
- 从第一个操作数中减去第二个操作数 A - B 将得到 -10
* 把两个操作数相乘 A * B 将得到 200
/ 分子除以分母(float型数据,分母必须加.0) B / A 将得到 2
% 取模运算符,整除后的余数 B % A 将得到 0
++A 自增运算符,整数值增加 1(先+后执行) ++ A将得到 11
A++ 自增运算符,整数值增加 1(先执行后+) A++ 将得到 11
-- 自减运算符,整数值减少 1 A-- 将得到 9

关系运算符

假设变量 A 的值为 10,变量 B 的值为 20,则:

运算符 描述 实例
== 检查两个操作数的值是否相等,如果相等则条件为真。 (A == B) 为假。
!= 检查两个操作数的值是否相等,如果不相等则条件为真。 (A != B) 为真。
> 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 (A > B) 为假。
< 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 (A < B) 为真。
>= 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 (A >= B) 为假。
<= 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 (A <= B) 为真。

逻辑运算符

假设变量 A 的值为 1,变量 B 的值为 0,则:

运算符 描述 实例
&& 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 (A && B) 为假。
|| 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 (A || B) 为真。
! 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 !(A && B) 为真。

位运算符

位运算符作用于位,并逐位执行操作。&、 | 和 ^ 的真值表如下所示:

p q p & q p | q p ^ q
0 0 0 0 0
0 1 0 1 1
1 1 1 1 0
1 0 0 1 1

赋值运算符

运算符 描述 实例
= 简单的赋值运算符,把右边操作数的值赋给左边操作数 C = A + B 将把 A + B 的值赋给 C
+= 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 C += A 相当于 C = C + A
-= 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 C -= A 相当于 C = C - A
*= 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 C = A 相当于 C = C A
/= 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 C /= A 相当于 C = C / A
%= 求模且赋值运算符,求两个操作数的模赋值给左边操作数 C %= A 相当于 C = C % A
<<= 左移且赋值运算符 C <<= 2 等同于 C = C << 2
>>= 右移且赋值运算符 C >>= 2 等同于 C = C >> 2
&= 按位与且赋值运算符 C &= 2 等同于 C = C & 2
^= 按位异或且赋值运算符 C ^= 2 等同于 C = C ^ 2
|= 按位或且赋值运算符 C |= 2 等同于 C = C | 2

其他运算符

运算符 描述 实例
sizeof() 返回变量的大小。 sizeof(a) 将返回 4,其中 a 是整数。
& 返回变量的地址。 &a; 将给出变量的实际地址。
* 指向一个变量。 *a; 将指向一个变量。
? : 条件表达式 如果条件为真 ? 则值为 X : 否则值为 Y

运算符优先级

类别 运算符 结合性
后缀 () [] -> . ++ - - 从左到右
一元 + - ! ~ ++ - - (type)* & sizeof 从右到左
乘除 * / % 从左到右
加减 + - 从左到右
移位 << >> 从左到右
关系 < <= > >= 从左到右
相等 == != 从左到右
位与 AND & 从左到右
位异或 XOR ^ 从左到右
位或 OR | 从左到右
逻辑与 AND && 从左到右
逻辑或 OR || 从左到右
条件 ?: 从右到左
赋值 = += -= *= /= %=>>= <<= &= ^= |= 从右到左
逗号 , 从左到

判断

判断结构要求程序员指定一个或多个要评估或测试的条件,以及条件为真时要执行的语句(必需的)和条件为假时要执行的语句(可选的)。

语句 描述
if语句 一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。
if...else语句 一个 if 语句 后可跟一个可选的 else 语句,else 语句在布尔表达式为假时执行。
嵌套if语句 您可以在一个 ifelse if 语句内使用另一个 ifelse if 语句。
switch语句 一个 switch 语句允许测试一个变量等于多个值时的情况。
嵌套switch语句 您可以在一个 switch 语句内使用另一个 switch 语句。

if语句

一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。

语法

1
2
3
4
if(条件)
{
/* 如果布尔表达式为真将执行的语句 */
}

if...else语句

一个 if 语句 后可跟一个可选的 else 语句,else 语句在布尔表达式为 false 时执行。

语法

1
2
3
4
5
6
7
8
if(条件)
{
/* 如果布尔表达式为真将执行的语句 */
}
else
{
/* 如果布尔表达式为假将执行的语句 */
}

if...else if...else语句

一个 if 语句后可跟一个可选的 else if...else 语句,这可用于测试多种条件。

  • 一个 if 后可跟零个或一个 else,else 必须在所有 else if 之后。
  • 一个 if 后可跟零个或多个 else if,else if 必须在 else 之前。
  • 一旦某个 else if 匹配成功,其他的 else if 或 else 将不会被测试。

语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if(条件)
{
/* 当布尔表达式 1 为真时执行 */
}
else if( boolean_expression 2)
{
/* 当布尔表达式 2 为真时执行 */
}
else if( boolean_expression 3)
{
/* 当布尔表达式 3 为真时执行 */
}
else
{
/* 当上面条件都不为真时执行 */
}

嵌套if语句

可以在一个 ifelse if 语句内使用另一个 ifelse if 语句。

语法

1
2
3
4
5
6
7
8
if(条件1)
{
/* 当布尔表达式 1 为真时执行 */
if(条件2)
{
/* 当布尔表达式 2 为真时执行 */
}
}

switch语句

一个 switch 语句允许测试一个变量等于多个值时的情况。每个值称为一个 case,且被测试的变量会对每个 switch case 进行检查。

  • switch 语句中的条件必须是一个变量。
  • 在一个 switch 中可以有任意数量的 case 语句。每个 case 后跟一个要比较的值和一个冒号。
  • case 的 确定值必须与 switch 中的变量具有相同的数据类型,且必须是一个常量或字面量。
  • 当被测试的变量等于 case 中的常量时,case 后跟的语句将被执行,直到遇到 break 语句为止。
  • 当遇到 break 语句时,switch 终止,控制流将跳转到 switch 语句后的下一行。
  • 不是每一个 case 都需要包含 break。如果 case 语句不包含 break,控制流将会 继续 后续的 case,直到遇到 break 为止。
  • 一个 switch 语句可以有一个可选的 default case,出现在 switch 的结尾。default case 可用于在上面所有 case 都不为真时执行一个任务。default case 中的 break 语句不是必需的。

语法

1
2
3
4
5
6
7
8
9
10
11
12
switch(变量)
{
case 确定值1
语句1
break;/*可选的*/
case 确定值2
语句2
break;/*可选的*/
/*可以任意数量的case语句*/
default:/*可选的*/
语句3
}

嵌套switch语句

可以在一个 switch 语句内使用另一个 switch 语句

语法

1
2
3
4
5
6
7
8
9
10
11
12
switch(ch1) {
case 'A':
printf("这个 A 是外部 switch 的一部分" );
switch(ch2) {
case 'A':
printf("这个 A 是内部 switch 的一部分" );
break;
case 'B': /* 内部 B case 代码 */
}
break;
case 'B': /* 外部 B case 代码 */
}

循环

循环语句允许我们多次执行一个语句或语句组

循环类型 描述
while循环 当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。
for循环 多次执行一个语句序列,简化管理循环变量的代码。
do...while循环 除了它是在循环主体结尾测试条件外,其他与 while 语句类似。
嵌套循环 您可以在 while、for 或 do..while 循环内使用一个或多个循环。

while循环

只要给定的条件为真, while 循环语句会重复执行一个目标语句。

语法

1
2
3
4
while(条件)
{
语句;
}
  • while条件可以为任意表达式,布尔值为False时直接跳出循环。
  • 执行语句可以是单条语句,也可以是代码块。

for循环

for 循环允许编写一个执行指定次数的循环控制结构。

语法

1
2
3
4
for (初始变量; 判断条件; 循环控制变量)
{
语句;
}
  1. 初始值会首先被执行,且只会执行一次。这一步允许您声明并初始化任何循环控制变量。您也可以不在这里写任何语句,只要有一个分号出现即可。

  2. 接下来,会判断条件。如果为真,则执行循环主体。如果为假,则不执行循环主体,且控制流会跳转到紧接着 for 循环的下一条语句。

  3. 在执行完 for 循环主体后,控制流会跳回上面的 循环控制变量。该语句允许更新循环控制变量。该语句可以留空,只要在条件后有一个分号出现即可。

  4. 条件再次被判断。如果为真,则执行循环,这个过程会不断重复(循环主体,然后增加步值,再然后重新判断条件)。在条件变为假时,for 循环终止

do...while循环

do...while 循环是在循环的尾部检查它的条件。do...while 循环与 while 循环类似,但是 do...while 循环会确保至少执行一次循环。

语法

1
2
3
4
5
do
{
statement(s);

}while( condition );
  • 条件表达式出现在循环的尾部,所以循环中的 statement(s) 会在条件被测试之前至少执行一次。

  • 如果条件为真,控制流会跳转回上面的 do,然后重新执行循环中的 statement(s)。这个过程会不断重复,直到给定条件变为假为止。

  • 最后while后面一定要加“;”。

嵌套循环

C 语言允许在一个循环内使用另一个循环。

嵌套 for 循环 语句的语法

1
2
3
4
5
6
7
8
9
10
for (initialization; condition; increment/decrement)
{
statement(s);
for (initialization; condition; increment/decrement)
{
statement(s);
... ... ...
}
... ... ...
}

嵌套 while 循环 语句的语法

1
2
3
4
5
6
7
8
9
10
while (condition1)
{
statement(s);
while (condition2)
{
statement(s);
... ... ...
}
... ... ...
}

嵌套 do...while 循环 语句的语法

1
2
3
4
5
6
7
8
9
10
do
{
statement(s);
do
{
statement(s);
... ... ...
}while (condition2);
... ... ...
}while (condition1);

循环控制语句

循环控制语句改变你代码的执行顺序。通过它可以实现代码的跳转。

控制语句 描述
break语句 终止循环switch 语句,程序流将继续执行紧接着循环或 switch 的下一条语句。
continue语句 告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。
goto语句 将控制转移到被标记的语句。但是不建议在程序中使用 goto 语句。

break语句

  1. break 语句出现在一个循环内时,循环会立即终止,且程序流将继续执行紧接着循环的下一条语句。
  2. 它可用于终止 switch 语句中的一个 case。

如果使用的是嵌套循环(即一个循环内嵌套另一个循环),break 语句会停止执行最内层的循环,然后开始执行该块之后的下一行代码。

continue语句

continue 会跳过当前循环中的代码,强迫开始下一次循环。

​ 1. 对于 for 循环,continue 语句执行后自增语句仍然会执行。

​ 2. 对于 whiledo...while 循环,continue 语句重新执行条件判断语句。

goto语句

允许把控制无条件转移到同一函数内的被标记的语句。在任何编程语言中,都不建议使用 goto 语句。

函数

函数是一组一起执行一个任务的语句。所有简单的程序都可以定义其他额外的函数。

定义函数

1
2
3
4
返回类型 函数名称(参数列表)
{
函数主体
}
  • 返回类型:一个函数可以返回一个值。有些函数执行所需的操作而不返回值,在这种情况下,返回类型是关键字 void
  • 函数名称:这是函数的实际名称。函数名和参数列表一起构成了函数签名。
  • 参数:当函数被调用时,向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。
  • 函数主体:函数主体包含一组定义函数执行任务的语句。

函数声明

return_type function_name( parameter list );

举例如下:

1
int max(int num1, int num2);

在函数声明中,参数的名称并不重要,只有参数的类型是必需的,因此下面也是有效的声明:

1
int max(int, int);

在一个源文件中定义函数且在另一个文件中调用函数时,函数声明是必需的。在这种情况下,应该在调用函数的文件顶部声明函数。

调用函数

通过调用函数来完成已定义的任务。

调用函数时,传递所需参数,如果函数返回一个值,则可以存储返回值,例如c=max(a,b);

函数参数

如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数

形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。

当调用函数时,有两种向函数传递参数的方式:

调用类型 描述
传值调用 该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。
引用调用 通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。

默认情况下,C 使用传值调用来传递参数。一般来说,这意味着函数内的代码不能改变用于调用函数的实际参数。

传值方式调用函数

向函数传递参数的传值调用方法,把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。

默认情况下,C 语言使用传值调用方法来传递参数。一般来说,这意味着函数内的代码不会改变用于调用函数的实际参数。函数 swap() 定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>

/* 函数声明 */
void swap(int x, int y);

int main ()
{
/* 局部变量定义 */
int a = 100;
int b = 200;

printf("交换前,a 的值: %d\n", a );
printf("交换前,b 的值: %d\n", b );

/* 调用函数来交换值 */
swap(a, b);

printf("交换后,a 的值: %d\n", a );
printf("交换后,b 的值: %d\n", b );

return 0;
}


/* 函数定义 */
void swap(int x, int y)
{
int temp;

temp = x; /* 保存 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 temp 赋值给 y */

return;
}

当上面的代码被编译和执行时,它会产生下列结果:

交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 100
交换后,b 的值: 200
上面的实例表明了,虽然在函数内改变了 a 和 b 的值,但是实际上 a 和 b 的值没有发生变化。

引用方式调用函数

通过引用传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。

传递指针可以让多个函数访问指针所引用的对象,而不用把对象声明为全局可访问。

1
2
3
4
5
6
7
8
9
10
/* 函数定义 */
void swap(int *x, int *y)
{
int temp;
temp = *x; /* 保存地址 x 的值 */
*x = *y; /* 把 y 赋值给 x */
*y = temp; /* 把 temp 赋值给 y */

return;
}

实例


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>

*/\* 函数声明 \*/*
void swap(int *x, int *y);

int main ()
{
*/\* 局部变量定义 \*/*
int a = 100;
int b = 200;

printf("交换前,a 的值: %d**\n**", a );
printf("交换前,b 的值: %d**\n**", b );

*/\* 调用函数来交换值
\* &a 表示指向 a 的指针,即变量 a 的地址
\* &b 表示指向 b 的指针,即变量 b 的地址
\*/*
swap(&a, &b);

printf("交换后,a 的值: %d**\n**", a );
printf("交换后,b 的值: %d**\n**", b );

return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

1
2
3
4
交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 200
交换后,b 的值: 100

上面的实例表明了,与传值调用不同,引用调用在函数内改变了 a 和 b 的值,实际上也改变了函数外 a 和 b 的值。

数组

可以存储一个固定大小的相同类型元素的顺序集合。

申明数组

在 C 中声明一个数组,需要指定元素的类型和元素的数量,如下所示:

1
double balance[10];//一维数组申明,现在 balance 是一个可用的数组,可以容纳 10 个类型为 double 的数字。

一个二维数组,在本质上,是一个一维数组的列表。声明一个 3 行4列的二维整型数组

1
int x[3][4];
C 中的二维数组

数组初始化

在 C 中,可以逐个初始化数组,也可以使用一个初始化语句,如下所示:

1
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};//也可忽略数组大小,等价于double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};

多维数组可以通过在括号内为每行指定值来进行初始化。下面是一个带有 3 行 4 列的数组。

1
2
3
4
5
int a[3][4] = {  
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
};

内部嵌套的括号是可选的,下面的初始化与上面是等同的:

1
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

访问数组元素

数组元素可以通过数组名称加索引进行访问。元素的索引是放在方括号内,跟在数组名称的后边。例如:

1
double salary = balance[9];

二维数组中的元素是通过使用下标(即数组的行索引和列索引)来访问的。例如:

1
int val = a[2][3];

传递数组给函数

如果想要在函数中传递一个一维数组作为参数,必须以下面三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。同样地,也可以传递一个多维数组作为形式参数。

方式 1

形式参数是一个指针:

1
void myFunction(int *param) { . . . }

方式 2

形式参数是一个已定义大小的数组:

1
void myFunction(int param[10]) { . . . }

方式 3

形式参数是一个未定义大小的数组:

1
void myFunction(int param[]) { . . . }

从函数返回数组

C 语言不允许返回一个完整的数组作为函数的参数。但是,可以通过指定不带索引的数组名来返回一个指向数组的指针。

如果想要从函数返回一个一维数组,您必须声明一个返回指针的函数,如下:

1
int * myFunction() { . . . }

另外,C 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。

实例

下面代码会生成 10 个随机数,并使用数组来返回它们,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
\#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/* 要生成和返回随机数的函数 */
int * getRandom( ) {
static int r[10];
int i;
/* 设置种子 */
srand( (unsigned)time( NULL ) );
for ( i = 0; i < 10; ++i) {
r[i] = rand();
printf( "r[%d] = %d\n", i, r[i]);
}
return r;
}
/* 要调用上面定义函数的主函数 */
int main () {
/* 一个指向整数的指针 */
int *p;
int i;
p = getRandom();
for ( i = 0; i < 10; i++ ) {
printf( "*(p + %d) : %d\n", i, *(p + i));
}
return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
r[0] = 313959809
r[1] = 1759055877
r[2] = 1113101911
r[3] = 2133832223
r[4] = 2073354073
r[5] = 167288147
r[6] = 1827471542
r[7] = 834791014
r[8] = 1901409888
r[9] = 1990469526
*(p + 0) : 313959809
*(p + 1) : 1759055877
*(p + 2) : 1113101911
*(p + 3) : 2133832223
*(p + 4) : 2073354073
*(p + 5) : 167288147
*(p + 6) : 1827471542
*(p + 7) : 834791014
*(p + 8) : 1901409888
*(p + 9) : 1990469526

C 指向数组的指针

数组名是一个指向数组中第一个元素的常量指针。因此,在下面的声明中:

1
double balance[50];

balance 是一个指向 &balance[0] 的指针,即数组 balance 的第一个元素的地址。因此,下面的程序片段把 p 赋值为 balance 的第一个元素的地址:

1
2
3
4
double *p;
double balance[10];

p = balance;

使用数组名作为常量指针是合法的,反之亦然。因此,*(balance + 4) 是一种访问 balance[4] 数据的合法方式。

一旦您把第一个元素的地址存储在 p 中,您就可以使用 p、(p+1)、*(p+2) 等来访问数组元素。下面的实例演示了上面讨论到的这些概念:

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
\#include <stdio.h>  
int main () {
/* 带有 5 个元素的整型数组 */
double balance[5] = {1000.0, 2.0, 3.4, 17.0, 50.0};
double *p;
int i;
p = balance;
/* 输出数组中每个元素的值 */
printf( "使用指针的数组值\n");
for ( i = 0; i < 5; i++ ) {
printf("*(p + %d) : %f\n", i, *(p + i) );
}
printf( "使用 balance 作为地址的数组值\n");
for ( i = 0; i < 5; i++ ) {
printf("*(balance + %d) : %f\n", i, *(balance + i) );
}
return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

1
2
3
4
5
6
7
8
9
10
11
12
使用指针的数组值
*(p + 0) : 1000.000000
*(p + 1) : 2.000000
*(p + 2) : 3.400000
*(p + 3) : 17.000000
*(p + 4) : 50.000000
使用 balance 作为地址的数组值
*(balance + 0) : 1000.000000
*(balance + 1) : 2.000000
*(balance + 2) : 3.400000
*(balance + 3) : 17.000000
*(balance + 4) : 50.000000

在上面的实例中,p 是一个指向 double 型的指针,这意味着它可以存储一个 double 类型的变量。一旦我们有了 p 中的地址,*p 将给出存储在 p 中相应地址的值,正如上面实例中所演示的。

字符串

在 C 语言中,字符串实际上是使用 null 字符 \0 终止的一维字符数组。

字符串的申明与初始化

下面的声明和初始化创建了一个 RUNOOB 字符串。由于在数组的末尾存储了空字符,所以字符数组的大小比单词 RUNOOB 的字符数多一个。

1
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};//C 编译器会在初始化数组时,自动把 \0 放在字符串的末尾,所以可以省略

依据数组初始化规则,可以把上面的语句写成以下语句:

1
char site[] = "RUNOOB";

操作字符串的函数:

序号 函数 & 目的
1 strcpy(s1, s2); 复制字符串 s2 到字符串 s1。
2 strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。
3 strlen(s1); 返回字符串 s1 的长度。
4 strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
5 strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
6 strstr(s1, s2); 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h> 

#include <string.h>

int main () {

char str1[14] = "runoob";

char str2[14] = "google";

char str3[14];

int len ;

/* 复制 str1 到 str3 */ *

strcpy(str3, str1);

printf("strcpy( str3, str1) : %s\n", str3 );

*/* 连接 str1 和 str2 */ *

strcat( str1, str2); *

printf("strcat( str1, str2): %s\n", str1 ); *

*/* 连接后,str1 的总长度 */

len = strlen(str1);

printf("strlen(str1) : %d\n", len ); return 0;

}

当上面的代码被编译和执行时,它会产生下列结果:

1
2
3
strcpy( str3, str1) :  runoob
strcat( str1, str2): runoobgoogle
strlen(str1) : 12

作用域

地址和指针

学习计划

20210528

  1. 掌握数据类型、变量、算数运算、标准头文件的使用(stdio.h、math.h)

作业

  1. printf() 来验证 a++  ++a 同理 -- 也要试验一下
  2. 输入一个华氏度转换成摄氏度,和摄氏度转华氏度
  3. 92页第8题

20210529

  1. 条件(if-else  switch-case)和循环(for  while  do-while)

作业

  1. 程序框图理解——登录验证判断
graph TB
    id0[\开始\] --> 输入名称 --> id1{判断名称是否正确} --是--> 输入密码 --> id2{判断密码是否正确} --是--> id9[\结束\]
    id1 --否--> 输入名称
    id2 --否--> 输入密码
  1. 给题1设置三次验证机会

  2. 将“温度输入转换(华氏度/摄氏度)”写成交互式代码——完成

  3. 电脑随机生成一个1~100的数,用户有5次猜数机会——完成

  4. 用户输入年份进行闰年判断(无限循环,需要用户手动终止)——完成

20210601

  1. 函数类型定义、函数返回、参数传递

作业

  1. 把“温度转换”程序写成函数,由main函数传递参数返回计算结果显示给用户
  2. 把“猜数”程序写成函数
  3. 把“登录”程序写成函数
  4. 把“闰年”程序写成函数
  5. 把以上所有参数都整理到同一个文件中,按照1~4的选择进入不同程序

20210616

  1. 学习数组的声明
  2. 数据类型的定义(数据别名)
  3. 字符串的声明
  4. 数据检查
  5. 字符串库的使用<string.h>,了解但是需要用到
  6. 数据[100] 和 数组[] 的区别
  7. 字符串的输入输出
  8. 数组的输入输出
  9. 中文在字符串中的存储空间
  10. 数组(字符串)的传递、字符串指针(较难,暂不用看)

作业

  1. 用户输入一串英文,把所有的小写字母转大写,或所有大写字母转小写
  2. 单词统计
  3. 生日管理器
  4. 平均值计算器(9门科目,用户逐一输入后,计算平均值,数值检查)