1. 变量类型和数据类型
1.1 helloworld
1. 头文件引入
- #include 是一个预处理指令,用于引入头文件。在 C 语言中,常用头文件比如 <stdio.h> 是标准输入输出库,提供了如 printf()、scanf() 等基本输入输出函数。
- 注意:头文件通常包含在尖括号 < > 内,表示从标准库中引入。可以自定义头文件,用双引号 " " 引入。
2. 宏定义
- 功能:#define 是用于定义宏的预处理指令。宏类似于常量,它会在编译时被替换为定义的值。例如,这里的 PI 会在程序中所有用到的地方被替换为 3.14。
- 特点:宏通常用大写字母命名,以区别于变量,且无需分号结尾。
- 使用场景:通常用于定义常量(如 PI),或特定的短代码片段(如 #define SQUARE(x) ((x) * (x)))。
1.1.3 main函数
- main() 是 C 程序的入口函数,每个 C 程序必须有且仅有一个 main() 函数。
- 返回值类型:int 表示 main() 函数会返回一个整数,通常用 return 0; 表示正常结束。
- 参数:括号中的 void 表示 main() 函数没有参数。对于有参数的 main(),可以用 int argc, char *argv[] 接收命令行参数。
#include <stdio.h>
#define PI 3.14
int main(void) {
printf("Hello World\n");
printf("%.2f\n",PI);
return 0;
}
代码执行流程:
- 预处理指令先执行,#include 和 #define 在编译前会被处理。
- 编译器找到 main() 入口,依次执行 printf() 语句。
- printf(“Hello World\n”); 输出 “Hello World”。
- printf(“%.2f\n”, PI); 输出 PI 的值(格式化到两位小数)。
- return 0; 告知操作系统程序成功结束。
1.2 常量
在 C 语言中,常量是指在程序运行期间其值不会改变的量。常量可以提高代码的可读性和可维护性
1. 常量的定义
常量可以通过多种方式定义,主要包括:
-
字面常量:直接在代码中使用的固定值。
- 整数常量:如
10
,-5
,0
。 - 浮点常量:如
3.14
,-0.001
。 - 字符常量:如
'A'
,'z'
。 - 字符串常量:如
"Hello, World!"
。
- 整数常量:如
-
宏常量:使用
#define
指令定义的常量。#define PI 3.14
-
常量变量:使用
const
关键字定义的变量,其值在初始化后不能被修改。const int MAX_SIZE = 100;
2. 常量的类型
常量可以是多种数据类型,包括:
- 整型常量:如
int
,short
,long
。 - 浮点型常量:如
float
,double
。 - 字符型常量:如
char
。 - 字符串常量:表示字符数组,类型为
char[]
。
3. 使用常量的好处
- 可读性:使用常量可以使代码更易于理解。例如,使用
#define PI 3.14
比直接使用3.14
更清晰。 - 可维护性:如果需要更改常量的值,只需在一个地方修改,而不必在代码中多处查找和替换。
- 防止意外修改:使用
const
关键字定义的常量在程序中不能被修改,避免了意外的错误。
4. 常量的作用域
- 全局常量:使用
#define
或const
定义的常量可以在整个文件中使用。 - 局部常量:在函数内部定义的常量只能在该函数内使用。
5. 常量的示例
#include <stdio.h>
#define PI 3.14 // 宏常量
int main(void) {
const int MAX_SIZE = 100; // 常量变量
int array[MAX_SIZE]; // 使用常量定义数组大小
printf("Value of PI: %.2f\n", PI);
printf("Max size: %d\n", MAX_SIZE);
return 0;
}
6. 注意事项
- 常量的类型:确保常量的类型与使用场景相匹配,例如,整数常量不能直接赋值给浮点型变量。
- 使用
const
:尽量使用const
定义常量,以便在编译时进行类型检查。 - 宏常量的局限性:宏常量没有类型检查,可能导致意外的错误,因此在使用时要小心。
常量在 C 语言中是非常重要的概念,它们提高了代码的可读性和可维护性。通过合理使用常量,可以减少错误并使代码更加清晰。如果你有其他问题或需要更深入的解释,请随时告诉我!
#include <stdio.h>
#define PI 3.1415 // 常量
int main(void) {
// 圆的面积 s = PI x 半径的平方
// 圆的周长 l = 2 * PI * r
//int r = 3; // 变量
const int r = 3; // 只读变量
float s = PI * r * r;
float l = 2 * PI * r;
//printf("圆的周长为:%f\n", l); //18.849001
//printf("圆的面积为:%f\n", s); //28.273500
printf("圆的周长为:%.2f\n", l); // 指定小数点后保留2位, 对第3位做,4舍五入
printf("圆的面积为:%.2f\n", s); // 指定小数点后保留2位
return 0;
}
1.3 变量
在 C 语言中,变量是用于存储数据的命名内存位置。变量的值可以在程序运行期间改变。
1. 变量的定义
- 定义:变量的定义包括指定变量的类型和名称。
- 语法:
数据类型 变量名;
- 示例:
int age; // 整型变量 float salary; // 浮点型变量 char grade; // 字符型变量
2. 变量的初始化
- 初始化:在定义变量时,可以同时给它赋一个初始值。
- 语法:
数据类型 变量名 = 初始值;
- 示例:
int age = 25; // 整型变量初始化 float salary = 5000.50; // 浮点型变量初始化 char grade = 'A'; // 字符型变量初始化
3. 变量的作用域
-
局部变量:在函数内部定义的变量,只能在该函数内使用。
void function() { int localVar = 10; // 局部变量 }
-
全局变量:在所有函数外部定义的变量,可以在整个程序中使用。
int globalVar = 20; // 全局变量 void function() { printf("%d\n", globalVar); // 可以访问全局变量 }
4. 变量的类型
C 语言支持多种数据类型,主要包括:
-
基本数据类型:
int
:整型,存储整数。float
:单精度浮点型,存储小数。double
:双精度浮点型,存储更大范围的小数。char
:字符型,存储单个字符。
-
派生数据类型:
- 数组:相同类型元素的集合。
- 结构体:用户定义的数据类型,可以包含不同类型的变量。
- 联合体:可以存储不同类型的变量,但同一时间只能存储一个。
- 指针:存储变量地址的变量。
5. 变量的命名规则
- 有效字符:变量名可以包含字母、数字和下划线(
_
),但不能以数字开头。 - 大小写敏感:
age
和Age
是不同的变量。 - 避免使用关键字:变量名不能是 C 语言的保留字(如
int
,return
等)。
6. 变量的使用
-
赋值:可以通过赋值运算符
=
来给变量赋值。age = 30; // 赋值
-
表达式:变量可以参与各种运算。
int sum = age + 5; // 使用变量进行计算
7. 示例代码
#include <stdio.h>
int main(void) {
int age = 25; // 整型变量
float salary = 5000.50; // 浮点型变量
char grade = 'A'; // 字符型变量
printf("Age: %d\n", age);
printf("Salary: %.2f\n", salary);
printf("Grade: %c\n", grade);
return 0;
}
8. 注意事项
- 未初始化变量:使用未初始化的变量会导致未定义行为,可能输出随机值。
- 内存管理:局部变量在函数结束后会被销毁,而全局变量在程序结束时才会被销毁。
- 类型匹配:在赋值和运算时,确保变量类型匹配,避免类型不兼容的错误。
变量是 C 语言中存储和操作数据的基本单位。理解变量的定义、初始化、作用域、类型和命名规则是编写有效 C 语言程序的基础。
1.4 sizeof数据类型大小
1. sizeof
运算符
- 定义:
sizeof
是一个编译时运算符,用于返回数据类型或变量的大小。 - 语法:
sizeof(data_type) sizeof(variable_name)
2. 数据类型的大小
不同的数据类型在不同的平台上可能占用不同的字节数。以下是一些常见数据类型及其在大多数平台上的典型大小(以字节为单位):
数据类型 | 大小(字节) | 说明 |
---|---|---|
char | 1 | 存储单个字符 |
int | 4 | 存储整数 |
short | 2 | 存储短整型 |
long | 4 或 8 | 存储长整型(依平台而定) |
float | 4 | 存储单精度浮点数 |
double | 8 | 存储双精度浮点数 |
long double | 8 或 16 | 存储扩展精度浮点数(依平台而定) |
3. 使用 sizeof
-
获取变量大小:
int a = 10; printf("Size of a: %zu\n", sizeof(a)); // 输出 a 的大小
-
获取数据类型大小:
printf("Size of int: %zu\n", sizeof(int)); // 输出 int 类型的大小
-
获取数组大小:
int arr[10]; printf("Size of arr: %zu\n", sizeof(arr)); // 输出整个数组的大小 printf("Size of arr[0]: %zu\n", sizeof(arr[0])); // 输出单个元素的大小
4. 注意事项
- 返回值类型:
sizeof
返回值的类型是size_t
,通常用于表示内存大小。 - 计算数组大小:使用
sizeof
计算数组大小时,返回的是整个数组的字节数,而不是元素的数量。要获取元素数量,可以用sizeof(arr) / sizeof(arr[0])
。 - 结构体的大小:结构体的大小可能会受到内存对齐的影响,因此可能比其成员的总和大。
5. 示例代码
#include <stdio.h>
int main(void) {
int a = 10;
float b = 5.5;
char c = 'A';
printf("Size of int: %zu bytes\n", sizeof(int));
printf("Size of float: %zu bytes\n", sizeof(b));
printf("Size of char: %zu bytes\n", sizeof(c));
int arr[10];
printf("Size of array: %zu bytes\n", sizeof(arr));
printf("Number of elements in array: %zu\n", sizeof(arr) / sizeof(arr[0]));
return 0;
}
sizeof
是一个非常有用的运算符,可以帮助程序员了解不同数据类型和变量在内存中占用的空间。- 理解数据类型的大小对于内存管理、性能优化和避免溢出等方面都非常重要。
1.5 无符号整型
无符号整型(unsigned integer)是 C 语言中的一种数据类型,用于表示非负整数。
1. 定义
- 无符号整型:无符号整型是指不带符号位的整型数据类型,能够表示从 0 到某个正整数的范围。
- 基本类型:无符号整型的基本类型包括
unsigned int
、unsigned short
、unsigned long
和unsigned long long
。
2. 数据类型及其范围
不同的无符号整型在不同的平台上可能占用不同的字节数,以下是常见无符号整型及其范围(以字节为单位):
数据类型 | 大小(字节) | 范围 |
---|---|---|
unsigned char | 1 | 0 到 255 |
unsigned short | 2 | 0 到 65,535 |
unsigned int | 4 | 0 到 4,294,967,295 |
unsigned long | 4 或 8 | 0 到 4,294,967,295 或更大 |
unsigned long long | 8 | 0 到 18,446,744,073,709,551,615 |
3. 使用无符号整型
-
定义无符号整型变量:
unsigned int num = 10;
-
无符号整型的运算:无符号整型可以参与各种算术运算,如加法、减法、乘法和除法。
unsigned int a = 5; unsigned int b = 10; unsigned int sum = a + b; // sum 为 15
4. 特点
- 非负值:无符号整型只能表示非负整数,因此它的最小值为 0。
- 更大的正数范围:由于没有符号位,无符号整型可以表示比相应的有符号整型更大的正整数。
- 溢出行为:无符号整型在发生溢出时会回绕。例如,
unsigned int
的最大值加 1 会变为 0。
5. 注意事项
- 类型转换:在混合使用有符号和无符号整型时,可能会导致意外的结果。C 语言会自动进行类型提升,但要小心可能的溢出。
- 避免负数:无符号整型不适合表示可能为负的值,使用时要确保数据的合理性。
6. 示例代码
#include <stdio.h>
int main(void) {
unsigned int a = 10;
unsigned int b = 20;
unsigned int sum = a + b;
printf("Sum: %u\n", sum); // 输出 Sum: 30
unsigned int max = 4294967295; // unsigned int 的最大值
printf("Max: %u\n", max);
max += 1; // 溢出
printf("After overflow: %u\n", max); // 输出 After overflow: 0
return 0;
}
1.6 字符类型
字符类型是 C 语言中的一种基本数据类型,用于表示单个字符。
1. 定义
- 字符类型:字符类型用于存储单个字符,通常使用
char
关键字定义。 - 字符的表示:字符在计算机中以 ASCII 或 Unicode 编码的形式存储。
2. 数据类型及其范围
char
:标准字符类型,通常占用 1 字节(8 位),可以表示 256 个不同的字符(从 -128 到 127 或 0 到 255,取决于是否为有符号)。- 无符号字符:可以使用
unsigned char
来表示 0 到 255 的字符范围。 - 有符号字符:使用
signed char
来表示 -128 到 127 的字符范围。
3. 使用字符类型
-
定义字符变量:
char letter = 'A'; // 使用单引号定义字符
-
字符的 ASCII 值:字符在内存中以其 ASCII 值存储,可以通过强制类型转换获取。
char letter = 'A'; int asciiValue = (int)letter; // asciiValue 为 65
4. 字符串与字符数组
-
字符串:在 C 语言中,字符串是以
char
数组的形式表示,以空字符'\0'
结尾。char str[] = "Hello"; // 字符串 "Hello" 实际上是 {'H', 'e', 'l', 'l', 'o', '\0'}
-
字符串长度:可以使用
strlen()
函数计算字符串的长度(不包括空字符)。#include <string.h> size_t length = strlen(str); // length 为 5
5. 特点
- 字符的编码:字符类型的值可以是 ASCII 字符(如字母、数字、符号)或其他编码(如 UTF-8)。
- 字符运算:字符类型可以参与算术运算,例如可以进行加法和减法。
char a = 'A'; char b = a + 1; // b 为 'B'
6. 注意事项
- 字符常量:字符常量用单引号
' '
包围,而字符串常量用双引号" "
包围。 - 字符数组的结束:字符数组(字符串)必须以
'\0'
结尾,以便正确识别字符串的结束。 - 字符类型的大小:字符类型的大小通常为 1 字节,但在某些平台上可能会有所不同。
7. 示例代码
#include <stdio.h>
#include <string.h>
int main(void) {
char letter = 'A';
printf("Character: %c\n", letter); // 输出 Character: A
printf("ASCII value: %d\n", (int)letter); // 输出 ASCII value: 65
char str[] = "Hello";
printf("String: %s\n", str); // 输出 String: Hello
printf("Length of string: %zu\n", strlen(str)); // 输出 Length of string: 5
return 0;
}
1.7 实型
实型(浮点型)是 C 语言中的一种数据类型,用于表示带小数的数值。
1. 定义
- 实型:实型用于表示实数,包括整数和小数,通常使用
float
、double
和long double
来定义。 - 精度:实型的精度取决于其类型,
float
通常用于单精度浮点数,double
用于双精度浮点数。
2. 数据类型及其范围
数据类型 | 大小(字节) | 精度(有效数字位数) | 范围 |
---|---|---|---|
float | 4 | 6-7 | 约 ±3.4 × 10^38 |
double | 8 | 15-16 | 约 ±1.7 × 10^308 |
long double | 8 或 16 | 18-19 或更多 | 依平台而定,通常更大 |
3. 使用实型
-
定义实型变量:
float f = 3.14f; // 单精度浮点数 double d = 3.1415926535; // 双精度浮点数
-
科学计数法:可以使用科学计数法表示浮点数。
float e = 1.5e3; // 表示 1500.0
4. 运算
-
基本运算:实型变量可以参与加、减、乘、除等基本运算。
float a = 5.0f; float b = 2.0f; float sum = a + b; // sum 为 7.0
-
注意除法:在进行除法运算时,要确保分母不为零。
float result = a / b; // 确保 b 不为 0
5. 特点
- 精度问题:浮点数在计算时可能会出现精度损失,特别是在进行大量运算时。
- 溢出与下溢:浮点数在超出其表示范围时会发生溢出(变为无穷大)或下溢(接近零)。
6. 注意事项
- 初始化:在使用浮点数之前,确保它们已被初始化。
- 比较:由于浮点数的精度问题,直接比较两个浮点数可能会导致错误,通常使用一个小的容差值进行比较。
if (fabs(a - b) < epsilon) { /* 进行比较 */ }
7. 示例代码
#include <stdio.h>
#include <math.h>
int main(void) {
float f = 3.14f;
double d = 3.141592653589793;
printf("Float: %.2f\n", f); // 输出 Float: 3.14
printf("Double: %.15f\n", d); // 输出 Double: 3.141592653589793
float a = 5.0f;
float b = 2.0f;
float result = a / b;
printf("Result of division: %.2f\n", result); // 输出 Result of division: 2.50
return 0;
}
1.8 进制和转换
进制是表示数值的一种方式,常见的进制包括二进制、八进制、十进制和十六进制。
1. 进制的定义
- 进制:进制是数值表示中基数的概念,表示一个数值系统中使用的符号数量。
- 常见进制:
- 二进制(Base 2):使用 0 和 1 两个符号,广泛应用于计算机系统。
- 八进制(Base 8):使用 0 到 7 八个符号,常用于简化二进制表示。
- 十进制(Base 10):使用 0 到 9 十个符号,是人类日常生活中最常用的进制。
- 十六进制(Base 16):使用 0 到 9 和 A 到 F(或 a 到 f)共 16 个符号,常用于计算机编程和内存地址表示。
2. 各进制的特点
-
二进制:
- 每位的权重是 2 的幂次方。
- 例如,二进制数
1011
表示 (1 \times 2^3 + 0 \times 2^2 + 1 \times 2^1 + 1 \times 2^0 = 8 + 0 + 2 + 1 = 11)(十进制)。
-
八进制:
- 每位的权重是 8 的幂次方。
- 例如,八进制数
17
表示 (1 \times 8^1 + 7 \times 8^0 = 8 + 7 = 15)(十进制)。
-
十进制:
- 每位的权重是 10 的幂次方。
- 例如,十进制数
123
表示 (1 \times 10^2 + 2 \times 10^1 + 3 \times 10^0 = 100 + 20 + 3 = 123)。
-
十六进制:
- 每位的权重是 16 的幂次方。
- 例如,十六进制数
1A
表示 (1 \times 16^1 + 10 \times 16^0 = 16 + 10 = 26)(十进制)。
3. 进制转换
-
二进制转十进制:
- 将每位乘以对应的 2 的幂次方,然后求和。
-
十进制转二进制:
- 不断除以 2,记录余数,直到商为 0,然后反向读取余数。
-
八进制与十六进制转换:
- 可以先转换为十进制,再转换为目标进制,或者直接使用相应的转换规则。
4. 示例代码
以下是一个简单的 C 语言程序,演示如何进行进制转换:
#include <stdio.h>
int decimalToBinary(int n) {
int binary = 0, remainder, place = 1;
while (n > 0) {
remainder = n % 2;
binary += remainder * place;
n /= 2;
place *= 10;
}
return binary;
}
int main(void) {
int decimal = 11;
printf("Decimal: %d\n", decimal);
printf("Binary: %d\n", decimalToBinary(decimal)); // 输出二进制
return 0;
}
5. 注意事项
- 数值范围:不同进制的数值范围不同,特别是在计算机中,二进制的表示会受到位数限制。
- 精度问题:在浮点数的进制转换中,可能会出现精度损失。
- 字符表示:在编程中,十六进制常用于表示字节和内存地址,通常以
0x
前缀表示。
1.9 数据溢出
数据溢出是指在计算机中进行数值运算时,结果超出了数据类型所能表示的范围,导致错误或不可预期的结果。以下是关于数据溢出的知识点:
1. 定义
- 数据溢出:当计算结果超出变量类型的最大或最小值时,就会发生数据溢出。溢出可能导致程序崩溃、错误结果或安全漏洞。
2. 溢出的类型
-
整型溢出:发生在整型数据类型(如
int
、short
、long
)的运算中。- 正溢出:当一个正数加上一个正数,结果超出该类型的最大值。
- 负溢出:当一个负数减去一个正数,结果低于该类型的最小值。
-
浮点型溢出:发生在浮点数(如
float
、double
)的运算中。- 正溢出:结果超出浮点数的最大表示值,通常会变为无穷大(
inf
)。 - 下溢:结果接近零,但超出浮点数的最小表示值,可能会变为零。
- 正溢出:结果超出浮点数的最大表示值,通常会变为无穷大(
3. 溢出的表现
-
整型溢出:
- 正溢出示例:
int maxInt = 2147483647; // 32位整型最大值 int overflowed = maxInt + 1; // 结果为 -2147483648(回绕)
- 正溢出示例:
-
浮点型溢出:
- 正溢出示例:
double largeValue = 1e308; // 接近 double 的最大值 double overflowed = largeValue * 10; // 结果为 inf
- 正溢出示例:
4. 预防措施
- 数据类型选择:根据需要选择合适的数据类型,确保其范围足够大。
- 边界检查:在进行运算前,检查操作数是否会导致溢出。
- 使用库函数:某些编程语言和库提供了安全的数学运算函数,可以自动处理溢出情况。
5. 示例代码
整型溢出:
#include <stdio.h>
#include <limits.h>
int main(void) {
int maxInt = INT_MAX; // 获取整型最大值
printf("Max int: %d\n", maxInt);
int overflowed = maxInt + 1; // 整型溢出
printf("Overflowed value: %d\n", overflowed); // 输出溢出后的值
return 0;
}
6. 注意事项
- 未定义行为:在 C 语言中,整型溢出是未定义行为,可能导致程序崩溃或产生不可预测的结果。
- 调试工具:使用调试工具和静态分析工具可以帮助检测潜在的溢出问题。
- 文档和规范:遵循编程语言的文档和最佳实践,以减少溢出风险。
2. 运算符和分支循环语句
2.1 字符串
在 C 语言中,字符串是一种特殊的数据类型,用于表示一系列字符。以下是关于 C 语言字符串的知识点:
1. 定义
- 字符串:在 C 语言中,字符串是以字符数组的形式存储的,字符数组以空字符
'\0'
结尾,表示字符串的结束。
2. 字符串的表示
-
字符数组:字符串可以通过字符数组来定义和初始化。
char str1[] = "Hello"; // 自动添加 '\0' char str2[6] = "World"; // 明确指定大小
-
字符串字面量:字符串字面量是以双引号包围的字符序列,例如
"Hello, World!"
。
3. 字符串的基本操作
-
字符串长度:可以使用
strlen()
函数计算字符串的长度(不包括空字符)。#include <string.h> size_t length = strlen(str1); // length 为 5
-
字符串复制:使用
strcpy()
函数将一个字符串复制到另一个字符串。char str3[20]; strcpy(str3, str1); // 复制 str1 到 str3
-
字符串连接:使用
strcat()
函数将两个字符串连接在一起。strcat(str1, str2); // 将 str2 连接到 str1
-
字符串比较:使用
strcmp()
函数比较两个字符串。int result = strcmp(str1, str2); // 返回值:0 表示相等,负值表示 str1 < str2,正值表示 str1 > str2
4. 字符串的特点
- 以空字符结束:C 语言中的字符串必须以
'\0'
结尾,以便函数能够识别字符串的结束。 - 不可变性:字符串字面量是不可变的,尝试修改字符串字面量会导致未定义行为。
- 动态分配:可以使用动态内存分配(如
malloc
)来创建可变长度的字符串。
5. 注意事项
- 内存管理:在使用动态分配的字符串时,确保在不再需要时释放内存,以避免内存泄漏。
- 缓冲区溢出:在处理字符串时,确保目标数组足够大,以防止缓冲区溢出。
- 字符编码:C 语言字符串通常使用 ASCII 编码,但在处理多字节字符(如 UTF-8)时需谨慎。
6. 示例代码
以下是一个简单的 C 语言程序,演示字符串的基本操作:
#include <stdio.h>
#include <string.h>
int main(void) {
char str1[20] = "Hello";
char str2[] = "World";
// 字符串长度
printf("Length of str1: %zu\n", strlen(str1));
// 字符串连接
strcat(str1, str2);
printf("Concatenated string: %s\n", str1);
// 字符串比较
int result = strcmp(str1, "HelloWorld");
if (result == 0) {
printf("Strings are equal.\n");
} else {
printf("Strings are not equal.\n");
}
return 0;
}
2.2 格式化输入
在 C 语言中,格式化输入是指使用特定的格式来读取用户输入的数据。常用的函数是 scanf()
,它允许根据指定的格式字符串从标准输入(通常是键盘)读取数据。以下是关于 C 语言格式化输入的知识点:
1. scanf()
函数
- 定义:
scanf()
是 C 标准库中的一个函数,用于从标准输入读取格式化数据。 - 语法:
int scanf(const char *format, ...);
2. 格式字符串
- 格式说明符:格式字符串包含一个或多个格式说明符,用于指定输入数据的类型和格式。常见的格式说明符包括:
%d
:读取整数(int
)。%f
:读取浮点数(float
)。%lf
:读取双精度浮点数(double
)。%c
:读取单个字符(char
)。%s
:读取字符串(字符数组),以空格为分隔符。%u
:读取无符号整数(unsigned int
)。%x
:读取十六进制整数。
3. 使用 scanf()
-
基本用法:
int a; float b; printf("Enter an integer and a float: "); scanf("%d %f", &a, &b); // 读取一个整数和一个浮点数
-
读取字符串:
char str[100]; printf("Enter a string: "); scanf("%s", str); // 读取一个字符串,到空格停止
4. 注意事项
-
地址运算符:在使用
scanf()
时,必须使用地址运算符&
来获取变量的地址,除了字符串(字符数组)外。scanf("%d", &a); // 正确 scanf("%s", str); // 正确,不需要 &
-
缓冲区溢出:使用
%s
读取字符串时,要确保目标数组足够大,以防止缓冲区溢出。可以使用%Ns
的形式限制输入长度(N 为最大字符数)。scanf("%99s", str); // 最多读取 99 个字符
-
返回值:
scanf()
返回成功读取的项数,可以用来检查输入是否成功。int result = scanf("%d", &a); if (result != 1) { printf("Invalid input.\n"); }
5. 示例代码
以下是一个简单的 C 语言程序,演示格式化输入的基本用法:
#include <stdio.h>
int main(void) {
int a;
float b;
char str[100];
printf("Enter an integer and a float: ");
scanf("%d %f", &a, &b);
printf("You entered: %d and %.2f\n", a, b);
printf("Enter a string: ");
scanf("%99s", str); // 限制输入长度
printf("You entered: %s\n", str);
return 0;
}
scanf()
是 C 语言中用于格式化输入的主要函数,通过格式字符串可以灵活地读取不同类型的数据。- 在使用
scanf()
时,注意输入的有效性和安全性,以避免潜在的错误和安全问题。
2.3 算术运算符
在 C 语言中,算术运算符用于执行基本的数学运算。以下是关于 C 语言算术运算符的知识点:
1. 定义
- 算术运算符:算术运算符是用于执行数学运算的符号,包括加法、减法、乘法、除法和取余等。
2. 常见算术运算符
运算符 | 描述 | 示例 |
---|---|---|
+ | 加法 | a + b |
- | 减法 | a - b |
* | 乘法 | a * b |
/ | 除法 | a / b |
% | 取余(模) | a % b |
3. 运算符的优先级
- 运算符的优先级决定了在表达式中运算的顺序。一般来说,乘法和除法的优先级高于加法和减法。
- 可以使用括号
()
来改变运算顺序。
4. 整数与浮点数运算
-
整数运算:当两个整数相除时,结果也是整数,余数会被舍去。
int a = 5, b = 2; int result = a / b; // result 为 2
-
浮点数运算:如果至少有一个操作数是浮点数,结果将是浮点数。
float c = 5.0, d = 2.0; float result = c / d; // result 为 2.5
5. 取余运算符 %
- 取余运算符用于计算两个整数相除后的余数。
- 仅适用于整数类型,不能用于浮点数。
int e = 5, f = 2; int remainder = e % f; // remainder 为 1
6. 示例代码
以下是一个简单的 C 语言程序,演示算术运算符的基本用法:
#include <stdio.h>
int main(void) {
int a = 10, b = 3;
float c = 10.0, d = 3.0;
// 加法
printf("Addition: %d + %d = %d\n", a, b, a + b);
// 减法
printf("Subtraction: %d - %d = %d\n", a, b, a - b);
// 乘法
printf("Multiplication: %d * %d = %d\n", a, b, a * b);
// 整数除法
printf("Integer Division: %d / %d = %d\n", a, b, a / b);
// 浮点数除法
printf("Floating-point Division: %.2f / %.2f = %.2f\n", c, d, c / d);
// 取余
printf("Remainder: %d %% %d = %d\n", a, b, a % b);
return 0;
}
7. 注意事项
- 除以零:在进行除法运算时,确保分母不为零,以避免运行时错误。
- 类型转换:在混合使用整数和浮点数时,注意数据类型的转换,以确保结果的准确性。
2.4 逻辑运算
在 C 语言中,逻辑运算符用于执行布尔逻辑运算,通常用于条件判断和控制程序的流向。以下是关于 C 语言逻辑运算的知识点:
1. 定义
- 逻辑运算符:逻辑运算符用于对布尔值(真或假)进行操作,返回布尔结果(真或假)。
2. 常见逻辑运算符
运算符 | 描述 | 示例 |
---|---|---|
&& | 逻辑与(AND) | a && b |
` | ` | |
! | 逻辑非(NOT) | !a |
3. 运算符的特点
-
逻辑与 (
&&
):- 当且仅当两个操作数都为真时,结果为真。
- 短路求值:如果第一个操作数为假,则不再计算第二个操作数。
if (a > 0 && b > 0) { // 当 a 和 b 都大于 0 时执行 }
-
逻辑或 (
||
):- 只要有一个操作数为真,结果就为真。
- 短路求值:如果第一个操作数为真,则不再计算第二个操作数。
if (a > 0 || b > 0) { // 当 a 或 b 至少有一个大于 0 时执行 }
-
逻辑非 (
!
):- 取反操作符,将真变为假,假变为真。
if (!a) { // 当 a 为假时执行 }
4. 逻辑运算的结果
- 逻辑运算的结果通常是整型值,0 表示假,非零值表示真。
- 在 C 语言中,任何非零值都被视为真,0 被视为假。
5. 示例代码
以下是一个简单的 C 语言程序,演示逻辑运算符的基本用法:
#include <stdio.h>
int main(void) {
int a = 5, b = 10;
// 逻辑与
if (a > 0 && b > 0) {
printf("Both a and b are positive.\n");
}
// 逻辑或
if (a < 0 || b < 0) {
printf("At least one of a or b is negative.\n");
} else {
printf("Both a and b are non-negative.\n");
}
// 逻辑非
if (!a) {
printf("a is zero.\n");
} else {
printf("a is non-zero.\n");
}
return 0;
}
6. 注意事项
- 短路求值:在使用逻辑与和逻辑或时,注意短路求值的特性,这可能会影响程序的行为。
- 结合使用:逻辑运算符可以与其他运算符结合使用,但要注意运算符的优先级,以确保表达式的正确性。
2.5 三目运算符
在 C 语言中,三目运算符(也称为条件运算符)是一种简洁的条件表达式,用于根据条件的真假选择不同的值。以下是关于 C 语言三目运算的知识点:
1. 定义
- 三目运算符:三目运算符的语法为
condition ? expression1 : expression2
,其中:condition
是一个布尔表达式。expression1
是当条件为真时的返回值。expression2
是当条件为假时的返回值。
2. 语法
result = condition ? expression1 : expression2;
- 如果
condition
为真(非零),则result
的值为expression1
;如果为假(零),则result
的值为expression2
。
3. 使用示例
-
基本用法:
int a = 5, b = 10; int max = (a > b) ? a : b; // max 将被赋值为 10
-
嵌套使用:
int score = 85; char grade = (score >= 90) ? 'A' : (score >= 80) ? 'B' : (score >= 70) ? 'C' : 'D'; // grade 将被赋值为 'B'
4. 特点
- 简洁性:三目运算符可以使代码更简洁,尤其是在简单的条件判断中。
- 可读性:虽然三目运算符可以使代码更简洁,但在复杂的条件判断中,过度使用可能会降低代码的可读性。
5. 注意事项
- 优先级:三目运算符的优先级低于大多数其他运算符,因此在复杂表达式中使用括号可以提高可读性。
- 类型一致性:
expression1
和expression2
的类型应当一致,以避免类型不匹配的问题。
6. 示例代码
以下是一个简单的 C 语言程序,演示三目运算符的基本用法:
#include <stdio.h>
int main(void) {
int a = 5, b = 10;
// 使用三目运算符找出较大的数
int max = (a > b) ? a : b;
printf("Max: %d\n", max); // 输出 Max: 10
// 使用三目运算符给分数赋等级
int score = 85;
char grade = (score >= 90) ? 'A' :
(score >= 80) ? 'B' :
(score >= 70) ? 'C' : 'D';
printf("Grade: %c\n", grade); // 输出 Grade: B
return 0;
}
7. 总结
- 三目运算符是 C 语言中一种方便的条件表达式,适用于简单的条件判断。
- 在使用时要注意可读性和类型一致性,以确保代码的清晰和正确。
2.6 隐式类型转换与强制类型转换
在 C 语言中,类型转换是将一种数据类型的值转换为另一种数据类型的过程。主要有两种类型转换:隐式类型转换和强制类型转换。以下是关于这两种类型转换的知识点:
1. 隐式类型转换
-
定义:隐式类型转换(也称为自动类型转换)是指在表达式中,编译器自动将一种数据类型转换为另一种数据类型,而无需程序员显式指定。
-
发生场景:
- 当不同类型的操作数参与运算时,编译器会自动将较小类型转换为较大类型,以避免数据丢失。
- 例如,在整型和浮点型混合运算时,整型会被自动转换为浮点型。
-
示例:
int a = 5; float b = 2.0; float result = a + b; // a 被隐式转换为 float
2. 强制类型转换
-
定义:强制类型转换是指程序员显式地将一种数据类型转换为另一种数据类型,使用类型转换运算符。
-
语法:
(type) expression
其中
type
是目标数据类型,expression
是要转换的值。 -
示例:
double x = 5.7; int y = (int)x; // 强制将 double 转换为 int,结果为 5
-
注意事项:
- 强制类型转换可能导致数据丢失。例如,将浮点数转换为整数时,小数部分会被截断。
- 在进行强制类型转换时,要确保转换是合理的,以避免逻辑错误。
3. 隐式与强制类型转换的比较
特点 | 隐式类型转换 | 强制类型转换 |
---|---|---|
触发方式 | 自动进行 | 程序员显式指定 |
数据丢失 | 通常不会 | 可能会导致数据丢失 |
语法 | 无需额外语法 | 使用 (type) 语法 |
示例 | int a = 5; float b = a; | int y = (int)5.7; |
4. 示例代码
以下是一个简单的 C 语言程序,演示隐式类型转换和强制类型转换的基本用法:
#include <stdio.h>
int main(void) {
// 隐式类型转换
int a = 5;
float b = 2.0;
float result = a + b; // a 被隐式转换为 float
printf("Implicit conversion result: %.2f\n", result); // 输出 7.00
// 强制类型转换
double x = 5.7;
int y = (int)x; // 强制将 double 转换为 int
printf("Explicit conversion result: %d\n", y); // 输出 5
return 0;
}
5. 总结
- 隐式类型转换:由编译器自动处理,通常安全,但在某些情况下可能导致意外结果。
- 强制类型转换:由程序员控制,灵活但需谨慎使用,以避免数据丢失和逻辑错误。
2.7 if 语句
在 C 语言中,if
分支语句用于根据条件的真假来控制程序的执行流。以下是关于 if
分支语句的知识点:
1. 定义
if
语句:if
语句用于根据给定的条件执行特定的代码块。如果条件为真(非零),则执行相应的代码;如果条件为假(零),则跳过该代码块。
2. 语法
if (condition) {
// 当 condition 为真时执行的代码
}
condition
:一个布尔表达式,返回值为真或假。
3. 扩展用法
if-else
语句:可以在if
语句后添加else
语句,以处理条件为假时的情况。
if (condition) {
// 当 condition 为真时执行的代码
} else {
// 当 condition 为假时执行的代码
}
if-else if-else
语句:可以使用多个else if
来处理多个条件。
if (condition1) {
// 当 condition1 为真时执行的代码
} else if (condition2) {
// 当 condition2 为真时执行的代码
} else {
// 当所有条件都为假时执行的代码
}
4. 注意事项
-
条件表达式:
if
语句中的条件表达式可以是任何返回整数值的表达式,非零值被视为真,零被视为假。 -
代码块:如果
if
或else
后面只有一条语句,可以省略大括号{}
,但为了提高可读性,建议始终使用大括号。 -
嵌套使用:可以在
if
语句内部嵌套其他if
语句,但要注意代码的可读性。
5. 示例代码
以下是一个简单的 C 语言程序,演示 if
分支语句的基本用法:
#include <stdio.h>
int main(void) {
int score;
printf("Enter your score: ");
scanf("%d", &score);
// 使用 if 语句判断分数等级
if (score >= 90) {
printf("Grade: A\n");
} else if (score >= 80) {
printf("Grade: B\n");
} else if (score >= 70) {
printf("Grade: C\n");
} else {
printf("Grade: D\n");
}
return 0;
}
if
分支语句是 C 语言中最基本的控制结构之一,用于根据条件执行不同的代码块。- 可以通过
else
和else if
扩展if
语句,以处理多种条件。 - 使用
if
语句时,注意条件表达式的正确性和代码的可读性。
2.8 switch语句
1. 定义
switch
语句:switch
语句用于根据一个表达式的值(通常是整型或字符型)来选择执行的代码块。它可以替代多个if-else
语句,使代码更清晰。
2. 语法
switch (expression) {
case constant1:
// 当 expression 等于 constant1 时执行的代码
break;
case constant2:
// 当 expression 等于 constant2 时执行的代码
break;
// 可以有多个 case
default:
// 当 expression 不匹配任何 case 时执行的代码
}
expression
:一个整型或字符型的表达式。case
:每个case
后面跟一个常量值,如果expression
的值与某个case
的常量值匹配,则执行该case
下的代码。break
:用于终止switch
语句,防止继续执行后续的case
。default
:可选的,表示当没有任何case
匹配时执行的代码。
3. 特点
- 整型和字符型:
switch
语句的expression
必须是整型或字符型,不能是浮点型或字符串。 - 常量匹配:
case
后面的常量值必须是常量表达式,不能是变量。 - 隐式穿透:如果没有
break
,程序会继续执行下一个case
的代码,直到遇到break
或switch
结束。
4. 示例代码
以下是一个简单的 C 语言程序,演示 switch
语句的基本用法:
#include <stdio.h>
int main(void) {
int day;
printf("Enter a number (1-7) for the day of the week: ");
scanf("%d", &day);
switch (day) {
case 1:
printf("Monday\n");
break;
case 2:
printf("Tuesday\n");
break;
case 3:
printf("Wednesday\n");
break;
case 4:
printf("Thursday\n");
break;
case 5:
printf("Friday\n");
break;
case 6:
printf("Saturday\n");
break;
case 7:
printf("Sunday\n");
break;
default:
printf("Invalid input! Please enter a number between 1 and 7.\n");
}
return 0;
}
5. 注意事项
- 使用
break
:确保在每个case
结束时使用break
,以避免意外的隐式穿透。 - 默认情况:建议总是包含
default
情况,以处理无效输入或未考虑的情况。 - 可读性:在处理多个条件时,
switch
语句通常比多个if-else
语句更易读。
6. 总结
switch
语句是 C 语言中用于多分支选择的控制结构,适合用于处理多个可能的常量值。- 它通过
case
和default
来组织代码,使得条件判断更加清晰。 - 使用时要注意
break
的使用,以避免意外的代码执行。
2.9 while 循环
1. 定义
while
循环:while
循环是一种控制结构,用于重复执行一段代码,直到给定的条件为假。它适用于在循环次数不确定的情况下。
2. 语法
while (condition) {
// 当 condition 为真时执行的代码
}
condition
:一个布尔表达式,返回值为真(非零)或假(零)。
3. 工作原理
- 在进入循环之前,首先检查
condition
的值。 - 如果
condition
为真,执行循环体内的代码,然后再次检查condition
。 - 如果
condition
为假,退出循环,继续执行后面的代码。
4. 示例代码
以下是一个简单的 C 语言程序,演示 while
循环的基本用法:
#include <stdio.h>
int main(void) {
int count = 1;
// 使用 while 循环打印 1 到 5
while (count <= 5) {
printf("%d\n", count);
count++; // 更新计数器
}
return 0;
}
5. 注意事项
-
无限循环:如果
condition
永远为真,循环将永远执行,导致程序无法停止。确保在循环体内有条件更新。 -
循环体:可以在
while
循环中使用多条语句,通常需要使用大括号{}
来包围它们。 -
初始条件:在使用
while
循环时,确保在循环开始前初始化相关变量。
6. 总结
while
循环是 C 语言中用于重复执行代码的基本结构,适合用于循环次数不确定的情况。- 在使用时要注意条件的更新,以避免无限循环。
2.10 do-while语句
1. 定义
do-while
循环:do-while
循环是一种控制结构,用于重复执行一段代码,直到给定的条件为假。与while
循环不同的是,do-while
循环至少会执行一次循环体。
2. 语法
do {
// 循环体代码
} while (condition);
condition
:一个布尔表达式,返回值为真(非零)或假(零)。
3. 工作原理
- 首先执行
do
块中的代码。 - 然后检查
condition
的值。 - 如果
condition
为真,继续执行do
块中的代码;如果为假,退出循环。
4. 示例代码
以下是一个简单的 C 语言程序,演示 do-while
循环的基本用法:
#include <stdio.h>
int main(void) {
int count = 1;
// 使用 do-while 循环打印 1 到 5
do {
printf("%d\n", count);
count++; // 更新计数器
} while (count <= 5);
return 0;
}
5. 注意事项
-
至少执行一次:
do-while
循环保证循环体至少执行一次,即使条件一开始就是假。 -
条件检查:在循环结束时检查条件,因此可以在循环体中使用不依赖于条件的初始值。
-
无限循环:如果
condition
永远为真,循环将永远执行,导致程序无法停止。确保在循环体内有条件更新。
6. 总结
do-while
循环是 C 语言中用于重复执行代码的控制结构,适合需要至少执行一次的情况。- 在使用时要注意条件的更新,以避免无限循环。
3. for 循环与数组冒泡排序
3.1 for 循环
1. 定义
for
循环:for
循环是一种控制结构,用于在已知循环次数的情况下重复执行一段代码。它通常用于遍历数组或执行固定次数的操作。
2. 语法
for (initialization; condition; increment) {
// 循环体代码
}
initialization
:循环开始前执行的初始化语句,通常用于定义和初始化循环变量。condition
:一个布尔表达式,返回值为真(非零)或假(零)。当条件为假时,循环结束。increment
:每次循环结束后执行的语句,通常用于更新循环变量。
3. 工作原理
- 首先执行
initialization
语句。 - 然后检查
condition
的值。 - 如果
condition
为真,执行循环体中的代码,然后执行increment
语句。 - 重复检查
condition
,直到其为假,退出循环。
4. 示例代码
以下是一个简单的 C 语言程序,演示 for
循环的基本用法:
#include <stdio.h>
int main(void) {
// 使用 for 循环打印 1 到 5
for (int count = 1; count <= 5; count++) {
printf("%d\n", count);
}
return 0;
}
5. 注意事项
-
循环变量作用域:在
for
循环中定义的循环变量的作用域仅限于循环体内。 -
多重循环:可以在
for
循环中嵌套其他循环,但要注意代码的可读性。 -
空循环:可以将循环体留空,使用分号
;
表示,但要确保条件能正确终止循环。
6. 总结
for
循环是 C 语言中用于重复执行代码的控制结构,适合在已知循环次数的情况下使用。- 它通过初始化、条件检查和增量更新来控制循环的执行,通常用于遍历数组或执行固定次数的操作。
3.2 continue
1. 定义
continue
语句:continue
语句用于控制循环的执行流程。当在循环体内遇到continue
语句时,当前迭代将被终止,控制权将跳转到循环的下一次迭代。
2. 工作原理
- 在
for
、while
或do-while
循环中,当执行到continue
语句时,循环体中continue
之后的代码将被跳过。 - 对于
for
循环,控制权将跳转到增量部分;对于while
和do-while
循环,控制权将跳转到条件检查部分。
3. 示例代码
以下是一个简单的 C 语言程序,演示 continue
语句的基本用法:
#include <stdio.h>
int main(void) {
// 使用 for 循环打印 1 到 10,但跳过 5
for (int i = 1; i <= 10; i++) {
if (i == 5) {
continue; // 跳过 5
}
printf("%d\n", i);
}
return 0;
}
4. 注意事项
-
适用范围:
continue
语句只能在循环结构中使用,不能在其他控制结构(如if
、switch
)中单独使用。 -
可读性:虽然
continue
可以使代码更简洁,但在复杂的循环中使用时,可能会降低代码的可读性。 -
与
break
的区别:continue
语句仅跳过当前迭代,而break
语句则会终止整个循环。
5. 总结
continue
语句是 C 语言中用于控制循环执行流程的语句,允许跳过当前迭代并继续下一次迭代。- 它可以提高代码的灵活性,但在使用时要注意可读性和适用范围。
3.3 break
1. 定义
break
语句:break
语句用于立即终止当前的循环或switch
语句,控制权将跳转到循环或switch
语句之后的第一条语句。
2. 工作原理
- 在
for
、while
、do-while
循环或switch
语句中,当执行到break
语句时,当前循环或switch
将被终止。 - 控制权将转移到循环或
switch
语句之后的代码。
3. 示例代码
以下是一个简单的 C 语言程序,演示 break
语句在循环中的基本用法:
#include <stdio.h>
int main(void) {
// 使用 for 循环打印 1 到 10,但在遇到 5 时终止循环
for (int i = 1; i <= 10; i++) {
if (i == 5) {
break; // 终止循环
}
printf("%d\n", i);
}
printf("Loop terminated at 5.\n");
return 0;
}
4. 注意事项
-
适用范围:
break
语句可以在循环和switch
语句中使用,但不能在其他控制结构(如if
)中单独使用。 -
多重循环:在嵌套循环中,
break
只会终止最近的一个循环。如果需要终止外层循环,可以使用标签(label)结合goto
语句,但这种做法通常不推荐。 -
与
continue
的区别:break
语句终止整个循环,而continue
语句仅跳过当前迭代并继续下一次迭代。
5. 总结
break
语句是 C 语言中用于控制循环和switch
语句执行流程的语句,允许立即终止当前的循环或switch
。- 它可以提高代码的灵活性,但在使用时要注意适用范围和代码的可读性。
3.4 goto
1. 定义
goto
语句:goto
语句是一种无条件跳转语句,用于在程序中跳转到指定的标签位置。它可以用于控制程序的执行流,但通常不推荐使用。
2. 语法
goto label;
...
label:
// 代码块
label
:一个标识符,后面跟一个冒号:
,表示跳转的目标位置。
3. 工作原理
- 当程序执行到
goto
语句时,控制权将立即跳转到指定的标签位置,继续执行该位置之后的代码。
4. 示例代码
以下是一个简单的 C 语言程序,演示 goto
语句的基本用法:
#include <stdio.h>
int main(void) {
int count = 0;
loop_start:
count++;
printf("%d\n", count);
if (count < 5) {
goto loop_start; // 跳回到 loop_start 标签
}
printf("Loop terminated.\n");
return 0;
}
5. 注意事项
-
可读性:
goto
语句可能导致代码的可读性降低,尤其是在复杂的程序中。过多的goto
使用会使程序逻辑难以追踪。 -
结构化编程:现代编程语言提倡结构化编程,通常推荐使用循环、条件语句等控制结构来替代
goto
。 -
局部跳转:
goto
只能在同一函数内跳转,不能跨函数跳转。
6. 总结
goto
语句是 C 语言中用于无条件跳转的控制结构,允许程序跳转到指定的标签位置。- 尽管它提供了灵活性,但由于可能导致代码可读性差和维护困难,通常不推荐在实际编程中使用。
3.5 数组
1. 定义
- 数组:数组是一个数据结构,用于存储多个相同类型的元素。它们在内存中是连续存储的,可以通过索引访问每个元素。
2. 特点
- 固定大小:数组的大小在声明时确定,不能动态改变。
- 相同类型:数组中的所有元素必须是相同的数据类型。
- 索引访问:数组的元素通过索引访问,索引从 0 开始。
3. 语法
data_type array_name[array_size];
data_type
:数组中元素的数据类型(如int
、float
、char
等)。array_name
:数组的名称。array_size
:数组的大小,即可以存储的元素数量。
4. 示例代码
以下是一个简单的 C 语言程序,演示数组的基本用法:
#include <stdio.h>
int main(void) {
// 声明一个整型数组,大小为 5
int numbers[5] = {1, 2, 3, 4, 5};
// 访问和打印数组元素
for (int i = 0; i < 5; i++) {
printf("Element at index %d: %d\n", i, numbers[i]);
}
return 0;
}
5. 注意事项
-
越界访问:访问数组时要确保索引在有效范围内(0 到
array_size - 1
),否则会导致未定义行为。 -
初始化:数组可以在声明时初始化,也可以在后续代码中逐个赋值。
-
多维数组:C 语言支持多维数组(如二维数组),用于表示矩阵等数据结构。
6. 多维数组示例
以下是一个简单的 C 语言程序,演示二维数组的基本用法:
#include <stdio.h>
int main(void) {
// 声明一个 2x3 的整型二维数组
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
// 访问和打印二维数组元素
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("Element at [%d][%d]: %d\n", i, j, matrix[i][j]);
}
}
return 0;
}
7. 总结
- 数组是 C 语言中用于存储多个相同类型元素的基本数据结构,具有固定大小和索引访问的特点。
- 在使用数组时,要注意越界访问和初始化问题,并可以利用多维数组来处理更复杂的数据结构。
3.6 数组逆序
1. 定义
- 数组逆序:数组逆序是指将数组中的元素顺序反转,使得原数组的第一个元素变为最后一个元素,第二个元素变为倒数第二个元素,以此类推。
2. 逆序的目的
- 数组逆序常用于数据处理、算法实现和特定应用场景中,例如字符串反转、数据排序等。
3. 逆序算法
- 逆序通常可以通过交换数组元素的方式实现。具体步骤如下:
- 使用两个指针,一个指向数组的开始位置,另一个指向数组的结束位置。
- 交换这两个指针所指向的元素。
- 移动指针,向中间靠拢,直到两个指针相遇。
4. 示例代码
以下是一个简单的 C 语言程序,演示如何逆序一个整型数组:
#include <stdio.h>
void reverseArray(int arr[], int size) {
int start = 0; // 开始指针
int end = size - 1; // 结束指针
while (start < end) {
// 交换元素
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
// 移动指针
start++;
end--;
}
}
int main(void) {
int numbers[] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]);
printf("Original array: ");
for (int i = 0; i < size; i++) {
printf("%d ", numbers[i]);
}
printf("\n");
reverseArray(numbers, size);
printf("Reversed array: ");
for (int i = 0; i < size; i++) {
printf("%d ", numbers[i]);
}
printf("\n");
return 0;
}
5. 注意事项
-
边界条件:在逆序过程中,要确保指针不会越界,避免访问无效内存。
-
原地逆序:上述方法是原地逆序,不需要额外的空间,适合大多数情况。
-
稳定性:逆序操作是非稳定的,即相同元素的相对位置可能会改变。
6. 总结
- 数组逆序是将数组元素顺序反转的操作,常用于数据处理和算法实现。
- 通过交换元素的方法,可以有效地实现数组的逆序,通常采用双指针的方式进行操作。
3.7 冒泡排序
1. 定义
- 冒泡排序:冒泡排序是一种简单的排序算法,通过重复遍历待排序的数组,比较相邻元素并交换它们的位置,直到整个数组有序。其名称来源于较大的元素“像气泡一样”逐渐浮到数组的顶端。
2. 工作原理
- 冒泡排序的基本步骤如下:
- 从数组的第一个元素开始,依次比较相邻的两个元素。
- 如果前一个元素大于后一个元素,则交换它们的位置。
- 重复上述过程,直到没有需要交换的元素为止。
- 每次遍历后,最大的元素会被“冒泡”到数组的末尾。
3. 复杂度分析
-
时间复杂度:
- 最坏情况:O(n²)(当数组是逆序时)
- 最好情况:O(n)(当数组已经有序时)
- 平均情况:O(n²)
-
空间复杂度:O(1)(原地排序,不需要额外的存储空间)
4. 示例代码
以下是一个简单的 C 语言程序,演示冒泡排序的基本实现:
#include <stdio.h>
void bubbleSort(int arr[], int size) {
for (int i = 0; i < size - 1; i++) {
// 标志位,用于优化
int swapped = 0;
for (int j = 0; j < size - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换元素
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = 1; // 记录发生了交换
}
}
// 如果没有发生交换,数组已经有序
if (swapped == 0) {
break;
}
}
}
int main(void) {
int numbers[] = {64, 34, 25, 12, 22, 11, 90};
int size = sizeof(numbers) / sizeof(numbers[0]);
printf("Original array: ");
for (int i = 0; i < size; i++) {
printf("%d ", numbers[i]);
}
printf("\n");
bubbleSort(numbers, size);
printf("Sorted array: ");
for (int i = 0; i < size; i++) {
printf("%d ", numbers[i]);
}
printf("\n");
return 0;
}
5. 注意事项
-
稳定性:冒泡排序是稳定的排序算法,即相同元素的相对位置不会改变。
-
优化:可以通过引入标志位来优化,如果在某次遍历中没有发生交换,说明数组已经有序,可以提前结束排序。
-
适用场景:由于时间复杂度较高,冒泡排序适合用于小规模数据的排序,或作为教学示例。
6. 总结
- 冒泡排序是一种简单易懂的排序算法,通过相邻元素的比较和交换来实现排序。
- 尽管其时间复杂度较高,但因其简单性和稳定性,仍然在某些场合中被使用。
4. 指针
4.1 指针基本使用
1. 定义
- 指针:指针是一个变量,其值为另一个变量的地址。指针可以用来间接访问和操作内存中的数据。
2. 特点
- 内存地址:指针存储的是内存地址,而不是实际的数据值。
- 类型安全:指针的类型决定了它所指向的数据类型,在访问数据时的类型安全。
- 灵活性:指针允许动态内存分配、数组和字符串处理、函数参数传递等灵活操作。
####3. 指针的声明与使用
- 声明:使用
*
符号来声明指针变量。
data_type *pointer_name;
- 初始化:指针可以通过取地址运算符
&
来初始化。
int a = 10;
int *p = &a; // p 指向 a 的地址
- 解引用:使用
*
符号可以访问指针所指向的值。
int value = *p; // 获取 p 指向的值
4. 示例代码
以下是一个简单的 C 语言程序,演示指针的基本用法:
#include <stdio.h>
int main(void) {
int a = 10;
int *p = &a; // p 指向 a 的地址
printf("Value of a: %d\n", a);
printf("Address of a: %p\n", (void*)&a);
printf("Value of p (address of a): %p\n", (void*)p);
printf("Value pointed by p: %d\n", *p); // 解引用
// 修改 a 的值通过指针
*p = 20;
printf("New value of a: %d\n", a);
return 0;
}
5. 注意事项
-
未初始化指针:使用未初始化的指针会导致未定义行为,可能会引发程序崩溃。
-
空指针:可以将指针初始化为
NULL
,表示它不指向任何有效的内存地址。 -
指针运算:指针可以进行加减运算,但要注意类型的影响。例如,
p + 1
会使指针移动到下一个同类型元素的地址。 -
指针与数组:数组名可以视为指向数组首元素的指针,指针和数组有密切的关系。
6. 总结
- 指针是 C 语言中重要的特性,允许直接操作内存地址,提供了灵活性和高效性。
- 正确使用指针可以提高程序的性能和可扩展性,但也需要谨慎,以避免内存泄漏和未定义行为。
4.2 野指针
-
定义:野指针是指向未分配或已释放内存的指针。使用野指针会导致未定义行为,可能引发程序崩溃或数据损坏。
-
常见原因:
- 未初始化的指针。
- 指向已释放内存的指针。
- 指针越界访问。
-
示例:
#include <stdio.h>
int main(void) {
int *p; // 未初始化的指针
*p = 10; // 未定义行为,可能导致程序崩溃
return 0;
}
4.3 空指针
-
定义:空指针是指向地址为
NULL
的指针,表示它不指向任何有效的内存地址。 -
使用:在使用指针前,检查指针是否为
NULL
,以避免空指针解引用。 -
示例:
#include <stdio.h>
int main(void) {
int *p = NULL; // 初始化为空指针
if (p != NULL) {
*p = 10; // 安全访问
} else {
printf("Pointer is NULL.\n");
}
return 0;
}
4.4 悬空指针
-
定义:悬空指针是指向已释放内存的指针。使用悬空指针会导致未定义行为。
-
常见原因:
- 动态内存释放后未将指针置为
NULL
。
- 动态内存释放后未将指针置为
-
示例:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = (int *)malloc(sizeof(int));
*p = 10;
free(p); // 释放内存
p = NULL; // 避免悬空指针
return 0;
}
4.5 指针越界
-
定义:指针越界是指访问数组或内存块时,超出了其合法范围。会导致未定义行为,可能引发程序崩溃或数据损坏。
-
示例:
#include <stdio.h>
int main(void) {
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
for (int i = 0; i <= 5; i++) { // 越界访问
printf("%d\n", *(p + i)); // 未定义行为
}
return 0;
}
4.6 双重释放
-
定义:双重释放是指对同一块内存进行多次释放操作。会导致未定义行为,可能引发程序崩溃。
-
示例:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = (int *)malloc(sizeof(int));
free(p); // 第一次释放
free(p); // 第二次释放,未定义行为
return 0;
}
-
野指针:未初始化或指向已释放内存的指针。
-
空指针:指向
NULL
的指针,表示不指向任何有效内存。 -
悬空指针:指向已释放内存的指针。
-
指针越界:访问数组或内存块时超出其合法范围。
-
双重释放:对同一块内存进行多次释放操作。
-
最佳实践:
- 初始化指针,避免使用未初始化的指针。
- 释放内存后,将指针置为
NULL
。 - 在使用指针前,检查是否为
NULL
。 - 避免指针越界访问。
- 确保每块内存只释放一次。