目录
1.2. 浮点型(Floating-Point Types)
C语言是一种广泛使用的高级编程语言,它支持过程化编程、模块化编程和结构化编程。
一、数据类型
在C语言中,基本数据类型是构建程序的基础。这些类型允许声明变量以存储不同类型的数据。
以下是C语言中几种基本数据类型的汇总,包括整型(int)、浮点型(float, double)、字符型(char)。
1.1. 整型(Integer Types)
整型用于存储整数值,没有小数部分。
C语言标准定义了多种整型,但最基本的是int
,它的大小(即它能表示的数值范围)依赖于编译器和操作系统,但通常是16位、32位或64位。
示例:
#include <stdio.h>
int main() {
int a = 10;
float b = 3.14;
char c = 'A';
printf("整型: %d, 浮点型: %f, 字符型: %c\n", a, b, c);
return 0;
}
1.2. 浮点型(Floating-Point Types)
浮点型用于存储有小数部分的数值。C语言提供了两种基本的浮点类型:float
和double
。float
通常提供单精度(大约7位十进制数字的精度),而double
提供双精度(大约15-17位十进制数字的精度)。
示例:
#include <stdio.h>
int main() {
float pi = 3.14f; // 注意f后缀,表示这是一个float字面量
double e = 2.71828;
printf("pi = %f, e = %lf\n", pi, e); // 使用%lf打印double类型
return 0;
}
1.3. 字符型(Character Types)
字符型用于存储单个字符,如字母、数字或标点符号。C语言中的字符型是char
。在大多数系统上,char
类型实际上是使用整型来存储的,但它被特别设计来存储字符。
示例:
#include <stdio.h>
int main() {
char ch = 'A';
printf("ch = %c\n", ch); // 使用%c打印字符
printf("ch的ASCII码 = %d\n", ch); // 字符也可以按整数打印,显示其ASCII码
return 0;
}
1.4. 注意事项
- 在打印
float
和double
类型的变量时,%f
用于float
和double
,但当想要明确指出一个double
类型的变量时(尤其是在某些编译器或设置下),使用%lf
是更好的做法。 - 字符常量(如
'A'
)用单引号括起来,而字符串常量(如"Hello, World!"
)用双引号括起来。 - C语言还提供了其他整型,如
short
、long
、long long
以及它们的无符号版本(通过在类型前加unsigned
关键字),用于存储不同范围的整数值。 - 浮点数的精度是有限的,因此在进行浮点数运算时要特别小心,以避免精度丢失或舍入错误。
1.5 汇总表
关键字 | 说明 | 字节大小(通常) |
---|---|---|
char | 字符型,存储单个字符,与ASCII码对应 | 1字节 |
short | 短整型,有符号,表示较小范围的整数 | 2字节 |
int | 整型,有符号,表示中等范围的整数 | 4字节(32位系统),可能不同(其他系统) |
long | 长整型,有符号,表示较大范围的整数 | 4字节(32位系统),8字节(64位系统) |
float | 单精度浮点型,表示带小数部分的实数,精度有限 | 4字节 |
double | 双精度浮点型,表示带小数部分的实数,精度比float高 | 8字节 |
long double | 长双精度浮点型,表示高精度实数,精度比double更高 | 8字节(常见),可能不同(其他系统) |
signed | 有符号修饰符,用于明确指定数据类型为有符号(可省略,因为默认就是有符号) | - |
unsigned | 无符号修饰符,用于明确指定数据类型为无符号,只能表示非负数和零 | - |
说明:
- char:字符型数据类型,通常占用1个字节,用于存储单个字符,与ASCII码表对应。在C语言中,字符型数据也可以当作整型数据处理,其取值范围与具体的实现和是否有符号有关。
- short:短整型数据类型,通常占用2个字节,用于表示较小范围的整数。与int和long相比,short能够表示的整数范围更小。
- int:整型数据类型,通常占用4个字节(在32位系统下),用于表示中等范围的整数。在不同的系统或编译器中,int的字节大小可能有所不同,但通常不会小于2个字节。
- long:长整型数据类型,用于表示较大范围的整数。在32位系统下,long通常占用4个字节;在64位系统下,long通常占用8个字节。
- float:单精度浮点型数据类型,通常占用4个字节,用于表示带小数部分的实数。float类型的精度有限,适用于对精度要求不高的场合。
- double:双精度浮点型数据类型,通常占用8个字节,用于表示带小数部分的实数。与float相比,double类型的精度更高,适用于对精度要求较高的场合。
- long double:长双精度浮点型数据类型,用于表示高精度实数。在不同的系统或编译器中,long double的字节大小和精度可能有所不同。
- signed与unsigned:这两个关键字用于修饰整型数据类型,以明确指定数据类型为有符号或无符号。默认情况下,整型数据类型(如int、short、long)都是有符号的,即可以表示正数、负数和零。如果使用unsigned修饰符,则数据类型只能表示非负数和零。
上述字节大小是基于常见的系统和编译器实现得出的通常值。在实际编程中,由于不同的编译器和目标平台可能会有所不同,因此可以使用sizeof()
函数来获取数据类型在特定平台上的实际字节大小。
二、变量声明与初始化
在C语言中,变量在使用前需要先声明其类型,变量声明是告诉编译器将要使用的一个或多个变量的名称以及它们的数据类型。变量可以在声明时初始化,也可以稍后在程序中赋值。
2.1. 变量声明
变量声明指定了变量的类型,并给变量分配了内存空间(但不分配初始值,除非同时初始化)。
语法:
type variable_name;
其中type
是变量的数据类型(如int
、float
、char
等),variable_name
是变量的名称。
2.2. 变量初始化
变量初始化是在声明变量的同时给它赋予一个初始值。
语法:
type variable_name = initial_value;
其中initial_value
是与变量类型兼容的初始值。
2.3. 示例
#include <stdio.h>
int main() {
// 变量声明
int a;
float b;
char c;
// 变量初始化
int d = 10;
float e = 3.14;
char f = 'A';
// 声明并初始化
int g = 20;
double h = 2.71828;
// 使用变量
a = 5; // 赋值
printf("a = %d, d = %d\n", a, d);
printf("e = %f, f = %c\n", e, f);
printf("g = %d, h = %lf\n", g, h);
return 0;
}
2.4. 注意事项
- 变量名必须以字母或下划线
_
开头,后面可以跟任意数量的字母、数字或下划线。 - 变量名是区分大小写的。
- 变量在声明之后、初始化之前,其值是未定义的(对于局部变量而言)。对于全局变量和静态变量,如果未显式初始化,则它们的值会被自动初始化为0(对于数值类型)或
\0
(对于字符类型)。 - 初始化是可选的,但推荐在声明时初始化变量,以避免使用未定义的值。
- 在C99及以后的标准中,允许在for循环等控制结构的初始化部分中声明变量(被称为块作用域变量),但在C90及以前的标准中,所有变量都必须在块的开始处声明。
三、运算符
C语言中的运算符用于执行各种算术运算、关系测试、逻辑操作等。
3.1. 算术运算符
- 加法(
+
):用于加法运算。 - 减法(
-
):用于减法运算。 - 乘法(
*
):用于乘法运算。 - 除法(
/
):用于除法运算。如果两个操作数都是整数,则结果也将是整数(向下取整)。 - 取模(
%
):也称为模除或求余运算,返回两数相除的余数。
示例:
#include <stdio.h>
int main() {
int a = 10, b = 3;
printf("a + b = %d\n", a + b);
printf("a - b = %d\n", a - b);
printf("a * b = %d\n", a * b);
printf("a / b = %d\n", a / b);
printf("a %% b = %d\n", a % b); // 注意:%需要被转义
return 0;
}
3.2. 关系运算符
- 大于(
>
):如果左侧操作数大于右侧操作数,则结果为真(1)。 - 小于(
<
):如果左侧操作数小于右侧操作数,则结果为真(1)。 - 等于(
==
):如果两侧操作数相等,则结果为真(1)。 - 不等于(
!=
):如果两侧操作数不相等,则结果为真(1)。 - 大于等于(
>=
):如果左侧操作数大于等于右侧操作数,则结果为真(1)。 - 小于等于(
<=
):如果左侧操作数小于等于右侧操作数,则结果为真(1)。
示例:
#include <stdio.h>
int main() {
int x = 5, y = 10;
printf("x > y: %d\n", x > y);
printf("x < y: %d\n", x < y);
printf("x == y: %d\n", x == y);
printf("x != y: %d\n", x != y);
return 0;
}
3.3. 逻辑运算符
- 逻辑与(
&&
):如果两侧操作数都为真(非零),则结果为真(1)。 - 逻辑或(
||
):如果两侧操作数中至少有一个为真(非零),则结果为真(1)。 - 逻辑非(
!
):如果操作数为假(0),则结果为真(1);如果操作数为真(非零),则结果为假(0)。
示例:
#include <stdio.h>
int main() {
int a = 5, b = 10;
printf("a > 5 && b > 10: %d\n", a > 5 && b > 10);
printf("a > 5 || b > 5: %d\n", a > 5 || b > 5);
printf("!(a > b): %d\n", !(a > b));
return 0;
}
3.4. 位运算符
这些运算符直接对整数的二进制表示进行操作。
- 位与(&):对两个操作数的每一位进行与运算。
- 位或(|):对两个操作数的每一位进行或运算。
- 位非(~):对一个操作数的每一位进行非运算。
- 左移(<<):将一个操作数的二进制表示向左移动指定的位数。
- 右移(>>):将一个操作数的二进制表示向右移动指定的位数。
- 位异或(^):对两个操作数的每一位进行异或运算。
以下示例展示位运算符如何直接对整数的二进制表示进行操作:
#include <stdio.h>
int main() {
unsigned int a = 5; // 二进制: 0000 0101
unsigned int b = 3; // 二进制: 0000 0011
// 位与运算
unsigned int and_result = a & b; // 结果: 0000 0001,即1
printf("a & b = %u\n", and_result);
// 位或运算
unsigned int or_result = a | b; // 结果: 0000 0111,即7
printf("a | b = %u\n", or_result);
// 位非运算
unsigned int not_result = ~a; // 结果: 1111 1010(在32位系统中),取反每一位
// 注意:这里我们使用了unsigned int来避免符号扩展导致的误解
// 在打印时,可能只关心低8位的结果,但~a实际上会影响所有位
// 为了简化,只打印低8位的结果(假设在一个8位系统上或者只看低8位)
printf("~a (low 8 bits) = %02X\n", (not_result & 0xFF)); // 输出: FE
// 左移运算
unsigned int left_shift_result = a << 2; // 结果: 0001 0100,即20
printf("a << 2 = %u\n", left_shift_result);
// 右移运算
unsigned int right_shift_result = a >> 2; // 结果: 0000 0010,即2
printf("a >> 2 = %u\n", right_shift_result);
// 位异或运算
unsigned int xor_result = a ^ b; // 结果: 0000 0110,即6
printf("a ^ b = %u\n", xor_result);
return 0;
}
注意:
在位非运算的示例中,
~a
的结果取决于整数的位宽。在大多数现代计算机上,int
类型通常是32位的,所以~a
的结果将是一个32位的值,其中除了a
的对应位被取反外,其他位都是1。为了简化输出,只打印了低8位的结果(使用& 0xFF
进行掩码操作)。当处理位运算时,通常使用
unsigned
类型来避免符号扩展的复杂性。符号扩展是在有符号整数右移时发生的一种现象,其中最高有效位(符号位)被复制到新添加的位中。在打印二进制结果时,由于C语言没有直接的二进制格式说明符,通常使用十六进制(
%x
或%X
)或八进制(%o
)来查看位模式,或者手动将二进制位转换为字符串进行打印
3.5. 赋值运算符
这些运算符用于将值赋给变量。
- 简单赋值(=):将右侧操作数的值赋给左侧变量。
- 复合赋值:如+=、-=、*=等,它们将右侧操作数与左侧变量的当前值进行算术运算,并将结果赋回给左侧变量。
以下示例展示简单赋值运算符和复合赋值运算符的用法:
#include <stdio.h>
int main() {
int a, b;
// 简单赋值
a = 10;
printf("a = %d\n", a); // 输出: a = 10
// 复合赋值 +=
b = 5;
b += 3; // 等同于 b = b + 3
printf("b += 3; b = %d\n", b); // 输出: b = 8
// 复合赋值 -=
a -= 4; // 等同于 a = a - 4
printf("a -= 4; a = %d\n", a); // 输出: a = 6
// 复合赋值 *=
a *= 2; // 等同于 a = a * 2
printf("a *= 2; a = %d\n", a); // 输出: a = 12
// 复合赋值 /=
b /= 2; // 等同于 b = b / 2
printf("b /= 2; b = %d\n", b); // 输出: b = 4
// 复合赋值 %=
a = 10;
a %= 3; // 等同于 a = a % 3,即求余数
printf("a %= 3; a = %d\n", a); // 输出: a = 1
return 0;
}
运行这段代码将依次输出每个变量的值,展示了这些赋值运算符如何工作。这些运算符在编程中非常有用,因为它们可以使代码更简洁、更易于阅读。
3.6. 条件运算符(三元运算符)
条件运算符,通常也被称为三元运算符,是一种简洁的条件判断方式,它允许在单行代码中根据条件的真假来选择两个值中的一个。
- 格式:条件表达式 ? 值1 : 值2。
- 用途:如果条件表达式为真,则返回值1;否则返回值2。
以下是C语言中使用条件运算符的代码示例:
#include <stdio.h>
int main() {
int a = 10;
int b = 20;
int max;
// 使用条件运算符找出两个数中的较大值
max = (a > b) ? a : b;
printf("The larger of a and b is: %d\n", max); // 输出: The larger of a and b is: 20
// 另一个示例:判断一个数是正数、负数还是零
int num = -5;
char *result;
result = (num > 0) ? "Positive" : (num < 0) ? "Negative" : "Zero";
printf("The number is: %s\n", result); // 输出: The number is: Negative
return 0;
}
需要注意的是,虽然条件运算符可以使代码更简洁,但在某些情况下,过多的嵌套条件可能会降低代码的可读性。因此,在实际编程中,应根据具体情况合理使用条件运算符,以保持代码的清晰和易于理解。
3.7. 逗号运算符
- 用途:顺序执行两个表达式,并返回最后一个表达式的值。经常用于在需要执行多个操作但只关心最后一个操作结果的情况下,或者用于在for循环的初始化部分同时声明和初始化多个变量。
以下是一些使用逗号运算符的代码示例:
#include <stdio.h>
int main() {
int a, b, c;
// 使用逗号运算符同时初始化多个变量
a = (b = 5, c = 10, b + c); // 这里b被赋值为5,c被赋值为10,但逗号运算符返回的是b+c的结果,即15
// 注意:虽然a得到了15,但通常不建议这样使用逗号运算符进行赋值,因为它会降低代码的可读性
// 更清晰的写法是分别给b和c赋值,然后计算a = b + c
printf("a = %d (not recommended use of comma operator)\n", a); // 输出: a = 15
// 更常见的用法是在for循环中初始化多个变量
for (int i = 0, j = 10; i < 5; i++, j--) {
printf("i = %d, j = %d\n", i, j);
}
// 输出:
// i = 0, j = 10
// i = 1, j = 9
// i = 2, j = 8
// i = 3, j = 7
// i = 4, j = 6
// 在函数参数中使用逗号运算符(不推荐,因为会降低可读性)
printf("Result: %d\n", (a = 3, a * 2)); // 输出: Result: 6
// 这里a被赋值为3,但逗号运算符返回的是a*2的结果,即6
// 警告:下面的用法虽然技术上合法,但极易导致错误和难以调试的问题
// 因为它违反了直觉和代码的可读性原则
int x = (int)(printf("Hello, "), 42); // x将被赋值为42,但printf的返回值(打印的字符数)被忽略了
// 输出: Hello,
return 0;
}
重要提示:
- 尽管逗号运算符在技术上可以用于上述场景,但过度或不当使用会显著降低代码的可读性和可维护性。
- 在大多数情况下,建议使用更清晰的语法结构,如分别进行赋值和计算,或使用更明确的逻辑来控制程序的流程。
- 特别是在涉及赋值和返回值的场景中,应谨慎使用逗号运算符,以避免引入难以察觉的错误。
在编写代码时,始终将代码的可读性和清晰性放在首位,以确保其他开发者(包括未来的你)能够轻松理解和维护代码。
3.8. sizeof运算符
- 用途:计算数据类型或变量在内存中所占的字节数。
- 它是一个编译时运算符,意味着它不会在运行时实际计算内存大小,而是在编译阶段就确定了结果。
sizeof
的结果类型是size_t
,这是一个无符号整数类型,定义在<stddef.h>
头文件中。
以下是使用 sizeof
运算符的一些代码示例:
#include <stdio.h>
int main() {
int a = 10;
double b = 3.14;
char c = 'A';
char str[] = "Hello, World!";
// 计算基本数据类型的大小
printf("Size of int: %zu bytes\n", sizeof(int));
printf("Size of double: %zu bytes\n", sizeof(double));
printf("Size of char: %zu bytes\n", sizeof(char));
// 计算变量的大小(对于基本类型,与数据类型的大小相同)
printf("Size of variable a (int): %zu bytes\n", sizeof(a));
printf("Size of variable b (double): %zu bytes\n", sizeof(b));
printf("Size of variable c (char): %zu bytes\n", sizeof(c));
// 计算数组的大小(数组总大小 = 元素大小 * 元素数量)
// 注意:sizeof(str) 计算的是整个数组的大小,包括末尾的空字符 '\0'
printf("Size of array str[]: %zu bytes\n", sizeof(str));
printf("Number of elements in array str[]: %zu\n", sizeof(str) / sizeof(str[0]));
// 计算指针的大小(指针大小与平台相关)
int *ptr = &a;
printf("Size of pointer: %zu bytes\n", sizeof(ptr));
// 计算结构体的大小(可能包括填充字节以对齐成员)
struct MyStruct {
char ch;
int i;
double d;
};
struct MyStruct s;
printf("Size of struct MyStruct: %zu bytes\n", sizeof(struct MyStruct));
printf("Size of variable s (MyStruct): %zu bytes\n", sizeof(s));
return 0;
}
实际运行结果:
计算了基本数据类型(int
、double
、char
)、变量、数组、指针和结构体的大小。注意以下几点:
sizeof
运算符的结果类型是size_t
,它是一个无符号整数类型,能够表示对象的大小。- 对于数组,
sizeof
返回整个数组的大小(以字节为单位),包括数组的所有元素和末尾的空字符(对于字符串数组)。 - 对于指针,
sizeof
返回指针本身的大小,这与平台(即编译器和目标机器的架构)有关,通常是4字节或8字节。 - 对于结构体,
sizeof
返回结构体实例的总大小,可能包括由于成员对齐而产生的填充字节。结构体的大小可能不是其所有成员大小的简单相加。
在编写涉及内存管理的代码时,了解数据类型和变量的大小是非常重要的。sizeof
运算符是获取这些信息的有效工具。
3.9. 地址运算符(&)
- 用途:获取变量的内存地址。当对一个变量使用
&
运算符时,它会返回该变量在内存中的地址。这个地址是一个指针,指向该变量的存储位置。
以下是一些使用地址运算符 &
的代码示例:
#include <stdio.h>
int main() {
int a = 10;
float b = 3.14f;
char c = 'A';
// 获取变量的地址
int *p_a = &a;
float *p_b = &b;
char *p_c = &c;
// 打印变量的值和地址
printf("Value of a: %d, Address of a: %p\n", a, (void*)&a);
printf("Value of b: %f, Address of b: %p\n", b, (void*)&b);
printf("Value of c: %c, Address of c: %p\n", c, (void*)&c);
// 通过指针访问变量的值
printf("Value of a through pointer: %d\n", *p_a);
printf("Value of b through pointer: %f\n", *p_b);
printf("Value of c through pointer: %c\n", *p_c);
// 修改指针指向的值
*p_a = 20;
*p_b = 6.28f;
*p_c = 'B';
// 打印修改后的值和地址(地址不会改变)
printf("Modified Value of a: %d, Address of a: %p\n", a, (void*)&a);
printf("Modified Value of b: %f, Address of b: %p\n", b, (void*)&b);
printf("Modified Value of c: %c, Address of c: %p\n", c, (void*)&c);
return 0;
}
实际运行结果:
定义了三个变量 a
、b
和 c
,并分别获取了它们的地址。使用地址运算符 &
来获取这些变量的地址,并将地址存储在相应的指针变量 p_a
、p_b
和 p_c
中。然后,打印了这些变量的值和地址,并通过指针访问和修改了这些变量的值。
注意几点:
- 地址运算符
&
只能用于变量(包括数组元素和结构体成员),不能用于常量或字面量。- 指针变量的类型必须与它所指向的变量的类型相匹配。
- 在打印地址时,我们使用
%p
格式说明符,并将地址强制转换为void*
类型,以确保与%p
的兼容性。- 通过指针访问和修改变量的值时,要小心不要越界或访问未初始化的内存,可能会导致未定义行为或程序崩溃。
3.10. 间接寻址运算符(*)
- 用途:访问指针所指向的内存地址中的数据。也称为解引用运算符。因为它能够“解开”指针,让我们能够读取或修改指针所指向位置的值。
以下是一些使用间接寻址运算符 *
的代码示例:
#include <stdio.h>
int main() {
int value = 42;
int *ptr = &value; // 获取变量value的地址,并将其存储在指针ptr中
// 使用间接寻址运算符访问指针所指向的值
printf("The value pointed to by ptr is: %d\n", *ptr);
// 修改指针所指向的值
*ptr = 100;
printf("After modification, the value of value is: %d\n", value);
// 示例:指针与数组
int arr[] = {1, 2, 3, 4, 5};
int *arr_ptr = arr; // 数组名本身就是数组首元素的地址
printf("First element of array: %d\n", *arr_ptr);
printf("Second element of array: %d\n", *(arr_ptr + 1)); // 使用指针算术访问数组元素
// 示例:函数与指针
void printValue(int *p) {
printf("Value passed to function: %d\n", *p);
}
printValue(&value); // 传递变量的地址给函数
return 0;
}
实际运行结果:
通过这些示例,可以看到间接寻址运算符
*
在处理指针和访问内存中的数据时是多么重要。在使用指针时,要特别小心,确保指针被正确初始化,并且不要访问未分配的内存或越界访问数组。
通过深入理解这些运算符,将能够更有效地使用C语言进行编程,并编写出更加复杂和高效的代码。
3.11. 注意事项
- 逻辑运算符的结果通常是整数
1
(真)或0
(假),但在条件表达式(如if
语句)中,它们通常被隐式地转换为布尔值。 - 关系运算符的结果也是整数
1
(真)或0
(假),用于表示条件测试的结果。 - 在使用取模运算符时,如果左侧操作数是负数,则结果的符号取决于编译器和平台(有的平台结果为正,有的为负)。
- 在进行除法运算时,如果除数为零,将导致运行时错误(通常是除以零的错误)。
四、控制语句
在C语言中,控制语句用于控制程序的流程,根据条件或迭代的需要执行不同的代码块。下面是对几种常见的控制语句的汇总。
4.1. if-else 条件判断
if-else
语句用于基于不同条件执行不同代码块。
示例:
#include <stdio.h>
int main() {
int num = 10;
if (num > 0) {
printf("Positive number\n");
} else {
printf("Non-positive number\n");
}
return 0;
}
4.2. for 循环
for
循环用于重复执行一段代码固定次数。
示例:
#include <stdio.h>
int main() {
for (int i = 0; i < 5; i++) {
printf("Number %d\n", i);
}
return 0;
}
4.3. while 循环
while
循环在给定条件为真时重复执行一段代码。
示例:
#include <stdio.h>
int main() {
int i = 0;
while (i < 5) {
printf("Number %d\n", i);
i++;
}
return 0;
}
4.4. do-while 循环
do-while
循环至少执行一次代码块,然后检查条件;如果条件为真,则继续执行。
示例:
#include <stdio.h>
int main() {
int i = 0;
do {
printf("Number %d\n", i);
i++;
} while (i < 5);
return 0;
}
4.5. switch 语句
switch
语句允许一个变量(或表达式)被测试,并使得程序可以跳转至多个不同代码块中的一个去执行。
示例:
#include <stdio.h>
int main() {
char grade = 'B';
switch (grade) {
case 'A':
printf("Excellent!\n");
break;
case 'B':
case 'C':
printf("Well done\n");
break;
case 'D':
printf("You passed\n");
break;
case 'F':
printf("Better try again\n");
break;
default:
printf("Invalid grade\n");
}
return 0;
}
4.6. 注意
- 在
switch
语句中,break
语句是必须的,除非希望执行完一个case
后自动进入下一个case
执行(这种用法被称为“fall-through”,但通常不是好的编程实践)。 if-else
、for
、while
和do-while
循环是流程控制的基础,几乎在每个程序中都会用到。switch
语句提供了一种比多个if-else
语句更清晰的方式来处理多个选项。但是,switch
语句中的表达式通常只限于整型(包括字符类型,因为字符在内部是以它们的ASCII值表示的整型)或枚举类型。在C99及以后的标准中,也可以是字符串字面量(尽管这在实践中不太常见)。
五、数组
数组是一种基础且强大的数据结构,它允许程序员以连续的内存空间存储相同类型的多个元素。在C语言中,数组是一个非常重要的概念,它被广泛用于各种编程任务中。以下是数组的汇总。【C语言进阶】数组与字符串_c 字符串数组-CSDN博客
5.1. 数组的基本特点
- 固定大小:数组在声明时就确定了可以存储的元素数量,这个数量在数组的生命周期内不会改变。
- 同类型元素:数组中的所有元素都必须是相同的数据类型。
- 索引访问:数组中的每个元素都可以通过索引(或下标)来访问,索引通常是从0开始的。
- 连续内存:数组中的元素在内存中连续存储,这使得访问数组元素非常快速。
5.2. 数组的声明和初始化
- 声明数组:
int arr[10]; // 声明一个可以存储10个整数的数组
- 初始化数组:
int arr[5] = {1, 2, 3, 4, 5}; // 声明并初始化一个包含5个整数的数组
如果初始化时提供的元素少于数组的大小,剩余的元素将自动初始化为0(对于数值类型数组)。
5.3. 数组的使用
- 访问数组元素:
int secondElement = arr[1]; // 访问数组的第二个元素(索引为1)
- 遍历数组:
for(int i = 0; i < 5; i++) {
printf("%d ", arr[i]); // 打印数组中的每个元素
}
5.4. 示例:计算数组的平均值
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int sum = 0;
float average;
// 计算数组元素的总和
for(int i = 0; i < 5; i++) {
sum += arr[i];
}
// 计算平均值
average = (float)sum / 5;
// 打印平均值
printf("Average: %.2f\n", average);
return 0;
}
5.5. 注意事项
- 数组越界:访问数组时,如果索引超出了数组的界限(即小于0或大于等于数组的大小),则会导致未定义行为,通常是程序崩溃或数据损坏。
- 数组大小是编译时确定的,因此数组的大小必须是常量表达式。
- 在C语言中,数组名代表数组首元素的地址,因此数组名在表达式中通常被当作指向数组首元素的指针。
六、字符串
在C语言中,字符串是通过字符数组来处理的,其中数组的最后一个元素是一个空字符(\0
),也称为null终止符,用于标识字符串的结束。使得C语言中的字符串具有固定的结束标志,从而便于函数如strlen
、strcpy
等知道何时停止处理字符串。
6.1. 字符串的定义
字符串可以通过字符数组直接定义,也可以在运行时通过字符数组赋值。
-
直接定义
char str[] = "Hello, World!";
这里,str
是一个字符数组,被初始化为包含字符串"Hello, World!"
和结尾的空字符\0
。
-
运行时赋值
char str[50];
strcpy(str, "Hello, World!");
这里,str
是一个可以存储49个字符加上一个结尾空字符的数组。使用strcpy
函数将字符串"Hello, World!"
复制到str
中。注意,这里需要确保目标数组str
有足够的空间来存储要复制的字符串,包括结尾的空字符。
6.2. 字符串的示例
下面是一个简单的C程序,展示了如何定义和使用字符串:
#include <stdio.h>
#include <string.h> // 引入字符串处理函数
int main() {
char str1[] = "Hello, ";
char str2[] = "World!";
char str3[50]; // 足够大以存储两个字符串拼接后的结果
// 字符串拼接
strcpy(str3, str1);
strcat(str3, str2); // strcat用于拼接字符串,需要包含<string.h>
// 打印结果
printf("Concatenated String: %s\n", str3);
// 获取字符串长度
printf("Length of the concatenated string: %lu\n", strlen(str3));
return 0;
}
首先定义了两个字符串str1
和str2
,然后使用strcpy
将str1
复制到str3
中,接着使用strcat
将str2
追加到str3
的末尾。最后,程序使用printf
和strlen
函数分别打印出拼接后的字符串和它的长度。
6.3. 注意事项
- 字符串在C语言中是以null终止的字符数组,意味着不能直接使用字符串字面量(如
"Hello, World!"
)作为函数参数,除非该函数明确设计为接受字符指针或字符数组(实际上,字符串字面量在大多数上下文中会被自动转换为指向其首字符的指针)。 - 使用
strcpy
、strcat
等字符串处理函数时,必须确保目标数组有足够的空间来存储结果字符串,包括结尾的空字符。否则,可能会发生缓冲区溢出,这是常见的安全漏洞之一。 - 在处理字符串时,经常需要包含
<string.h>
头文件,因为它提供了许多有用的字符串处理函数。
七、函数
在C语言中,函数是执行特定任务的独立代码块。通过定义函数,可以将复杂的程序分解为更小、更易于管理的部分,并且可以重用这些函数来执行相同的任务,而无需重复编写相同的代码。【C语言进阶】函数与模块_c语言模块-CSDN博客
7.1. 函数的定义
函数定义的一般形式如下:
返回类型 函数名(参数类型 参数名1, 参数类型 参数名2, ...) {
// 函数体
// 返回值语句(如果函数有返回值)
return 返回值;
}
- 返回类型:指定函数返回值的类型。如果函数不返回任何值,则使用
void
类型。 - 函数名:是函数的唯一标识符,用于调用函数。
- 参数列表:函数可以接收零个或多个参数,每个参数都包括参数类型和参数名。参数是可选的,用于向函数传递数据。
- 函数体:包含执行特定任务的语句。
- 返回值:如果函数有返回值,则使用
return
语句返回该值。如果函数类型为void
,则不需要return
语句,或者可以使用return;
来结束函数。
7.2. 函数的调用
调用函数的基本语法是:
函数名(参数1, 参数2, ...);
7.3. 示例:计算两个数的和
下面是一个简单的示例,展示了如何定义一个计算两个整数和的函数,并调用它。
#include <stdio.h>
// 定义一个函数,用于计算两个整数的和
int sum(int a, int b) {
return a + b;
}
int main() {
int num1 = 5, num2 = 10;
int result;
// 调用函数,并将结果存储在变量result中
result = sum(num1, num2);
// 打印结果
printf("The sum of %d and %d is %d\n", num1, num2, result);
return 0;
}
sum
函数接收两个整数作为参数,并返回它们的和。在main
函数中,我们调用了sum
函数,并将结果存储在变量result
中,然后打印出来。
7.4. 注意事项
- 函数定义必须在调用它之前,或者至少要在调用它的代码之前声明函数原型。函数原型告诉编译器函数的返回类型、名称和参数类型,但不包括函数体。
- 函数可以嵌套调用,即一个函数可以调用另一个函数。
- 递归函数是一种特殊的函数,它直接或间接地调用自身。递归函数需要谨慎使用,以避免无限递归和栈溢出。
- 函数的参数是通过值传递的,意味着传递给函数的参数是原始数据的副本。在函数内部对参数所做的任何修改都不会影响原始数据(除非参数是指针)。
八、指针
指针是C语言中一个极其重要的概念,它允许程序直接访问和操作内存地址。通过指针,我们可以动态地分配内存、传递数组或结构体等大型数据结构给函数,以及实现复杂的数据结构和算法。以下是对指针的汇总。【C语言进阶】指针详解-CSDN博客
8.1. 指针的基本概念
- 指针变量:指针变量用于存储变量的内存地址。指针变量的类型决定了它所能指向的变量的类型。
- 解引用:通过指针访问它所指向的变量的值的过程称为解引用。在C语言中,我们使用
*
运算符来解引用指针。 - 指针的声明:声明指针变量时,需要在变量类型前加上
*
符号。例如,int *ptr;
声明了一个指向int
类型变量的指针ptr
。 - 指针的赋值:指针可以被赋值为一个变量的地址,通常通过取地址运算符
&
来实现。例如,ptr = &var;
将变量var
的地址赋给指针ptr
。 - 指针的算术运算:指针可以进行算术运算,如递增(
ptr++
)和递减(ptr--
),这些操作实际上是按照指针所指向的数据类型的大小来移动指针的。
8.2. 指针的示例
示例1:基本指针操作
#include <stdio.h>
int main() {
int var = 20; // 声明一个整型变量
int *ptr; // 声明一个整型指针
ptr = &var; // 将var的地址赋给ptr
printf("Value of var: %d\n", var);
printf("Value of *ptr: %d\n", *ptr); // 解引用ptr,打印var的值
return 0;
}
示例2:指针与数组
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50}; // 声明一个整型数组
int *ptr; // 声明一个整型指针
ptr = arr; // 数组名在表达式中通常被当作指向数组首元素的指针
for(int i = 0; i < 5; i++) {
printf("Value of arr[%d]: %d\n", i, *(ptr + i)); // 使用指针算术访问数组元素
}
return 0;
}
示例3:动态内存分配
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr;
int n = 5; // 假设我们想动态分配一个包含5个整数的数组
// 使用malloc动态分配内存
ptr = (int*)malloc(n * sizeof(int));
if(ptr == NULL) {
printf("Memory not allocated.\n");
exit(0);
}
// 使用指针来访问和修改分配的内存
for(int i = 0; i < n; i++) {
*(ptr + i) = i * i; // 将平方值存储在数组中
}
// 打印数组内容
for(int i = 0; i < n; i++) {
printf("%d ", *(ptr + i));
}
// 释放分配的内存
free(ptr);
return 0;
}
8.3. 注意事项
- 指针必须在使用前被初始化,否则它们将包含不确定的值,可能导致程序崩溃或不可预测的行为。
- 使用指针时要小心内存泄漏,确保分配的内存最终被释放。
- 指针算术运算仅适用于指向数组元素的指针,并且结果指针必须指向数组的有效范围内。
- 指针解引用时必须确保指针不是
NULL
,否则会导致程序崩溃。
九、总结
C语言基础语法包括变量声明、条件语句(if-else)、循环结构(for, while, do-while)、函数定义与调用、指针操作、数组与字符串处理。掌握这些语法,能构建复杂程序逻辑,处理数据输入输出。通过实践和学习这些基础,可以进一步探索C语言的高级特性和应用。