C语言核心知识(第二部分:数组、指针、字符串)
一、数组
1.1 一维数组
// 声明和初始化
int arr[5]; // 声明未初始化
int arr2[5] = {1, 2, 3, 4, 5}; // 完全初始化
int arr3[] = {1, 2, 3}; // 自动计算大小
int arr4[5] = {1, 2}; // 部分初始化,其余为0
// 数组大小计算
int size = sizeof(arr) / sizeof(arr[0]);
// 数组遍历
for(int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
1.2 二维数组
// 声明和初始化
int matrix[3][4]; // 3行4列
int matrix2[2][3] = { // 直接初始化
{1, 2, 3},
{4, 5, 6}
};
int matrix3[][3] = { // 自动计算行数
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// 访问二维数组
for(int i = 0; i < 2; i++) {
for(int j = 0; j < 3; j++) {
printf("%d ", matrix2[i][j]);
}
printf("\n");
}
1.3 数组的存储特性
// 数组在内存中是连续存储的
int arr[3] = {1, 2, 3};
// 内存布局:
// arr[0] arr[1] arr[2]
// 1 2 3
// 地址: &arr &arr+1 &arr+2
// 数组名是首元素地址
printf("arr = %p\n", arr); // 等价于 &arr[0]
printf("&arr[0] = %p\n", &arr[0]);
// 但数组名不是指针常量
// arr = &some_var; // 错误!数组名不能赋值
二、指针
2.1 指针基础
// 声明指针
int a = 10;
int *p = &a; // p指向a的地址
// 使用指针
printf("a的值: %d\n", a); // 直接访问
printf("*p的值: %d\n", *p); // 间接访问
printf("a的地址: %p\n", &a);
printf("p的值: %p\n", p);
// 修改值
*p = 20; // 通过指针修改a的值
printf("修改后a: %d\n", a); // 输出20
2.2 指针运算
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr; // 指向数组首元素
// 指针加减运算
printf("ptr指向: %d\n", *ptr); // 10
ptr++; // 指向下一个元素
printf("ptr++后: %d\n", *ptr); // 20
ptr += 2; // 前进2个元素
printf("ptr+=2后: %d\n", *ptr); // 40
// 指针比较
int *p1 = &arr[0];
int *p2 = &arr[3];
if(p1 < p2) { // 比较地址
printf("p1在p2之前\n");
}
// 指针相减得到元素个数
int diff = p2 - p1; // diff = 3
printf("p2-p1 = %d个元素\n", diff);
2.3 指针的指针(多级指针)
int a = 100;
int *p = &a; // 一级指针
int **pp = &p; // 二级指针
int ***ppp = &pp; // 三级指针
// 访问值
printf("a = %d\n", a); // 100
printf("*p = %d\n", *p); // 100
printf("**pp = %d\n", **pp); // 100
printf("***ppp = %d\n", ***ppp);// 100
2.4 指针与数组的关系
int arr[3] = {1, 2, 3};
// 访问数组的四种方式
arr[0] = 10; // 1. 数组下标
*(arr + 0) = 20; // 2. 指针运算
int *p = arr; // 3. 指针变量
p[0] = 30; // 4. 指针下标
// 等价性证明
arr[i] ≡ *(arr + i)
*(arr + i) ≡ *(i + arr) ≡ i[arr] // 奇怪的写法但正确!
2.5 函数指针
// 普通函数
int add(int a, int b) {
return a + b;
}
// 声明函数指针
int (*func_ptr)(int, int);
// 赋值
func_ptr = add; // 函数名就是函数地址
// 或 func_ptr = &add;
// 通过指针调用函数
int result = func_ptr(10, 20); // 调用add(10, 20)
printf("10 + 20 = %d\n", result); // 输出30
// 函数指针数组
int (*operations[4])(int, int) = {
add, // 加法
sub, // 减法
mul, // 乘法
div // 除法
};
// 使用函数指针数组
for(int i = 0; i < 4; i++) {
printf("结果: %d\n", operations[i](10, 5));
}
2.6 void指针
// void指针是通用指针
int a = 100;
float b = 3.14;
char c = 'A';
void *vp;
vp = &a; // 可以指向任何类型
vp = &b;
vp = &c;
// 使用前需要类型转换
int *int_ptr = (int*)vp;
printf("值: %d\n", *int_ptr);
// 常用于通用函数
void swap(void *a, void *b, size_t size) {
char temp;
char *pa = (char*)a;
char *pb = (char*)b;
for(size_t i = 0; i < size; i++) {
temp = pa[i];
pa[i] = pb[i];
pb[i] = temp;
}
}
三、字符串
3.1 字符串基础
// 字符串的三种表示方式
char str1[] = "Hello"; // 字符数组
char str2[10] = "World"; // 指定大小
char *str3 = "String Literal"; // 字符串常量
// 字符串以'\0'结尾
char str[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
// 字符串长度(不包括'\0')
int len = strlen("Hello"); // len = 5
// 字符串输入输出
char name[20];
printf("请输入姓名: ");
scanf("%s", name); // 读取一个单词
fgets(name, 20, stdin); // 读取一行(安全)
printf("姓名: %s\n", name);
3.2 字符串函数
#include <string.h>
char str1[20] = "Hello";
char str2[20] = "World";
// 字符串复制
strcpy(str1, "New String"); // str1 = "New String"
strncpy(str1, "Safe Copy", 10); // 安全版本
// 字符串连接
strcat(str1, " "); // 追加空格
strcat(str1, str2); // str1 = "Hello World"
strncat(str1, "!", 1); // 安全版本
// 字符串比较
if(strcmp(str1, str2) == 0) {
printf("字符串相等\n");
} else if(strcmp(str1, str2) < 0) {
printf("str1 < str2\n");
} else {
printf("str1 > str2\n");
}
// 字符串查找
char *pos = strchr(str1, 'W'); // 查找字符
if(pos) printf("找到W在位置%ld\n", pos - str1);
pos = strstr(str1, "World"); // 查找子串
if(pos) printf("找到World\n");
// 字符串分割
char text[] = "apple,banana,orange";
char *token = strtok(text, ",");
while(token) {
printf("水果: %s\n", token);
token = strtok(NULL, ",");
}
3.3 字符串与指针
// 字符串常量
char *str = "Hello"; // "Hello"在只读内存区
// str[0] = 'h'; // 错误!不能修改字符串常量
// 字符数组
char arr[] = "Hello"; // 在栈上分配内存
arr[0] = 'h'; // 正确!可以修改
// 动态分配字符串
char *dynamic = (char*)malloc(20 * sizeof(char));
strcpy(dynamic, "Dynamic String");
printf("%s\n", dynamic);
free(dynamic);
3.4 字符处理函数
#include <ctype.h>
char c = 'A';
// 字符类型判断
if(isalpha(c)) printf("字母\n"); // 字母
if(isdigit(c)) printf("数字\n"); // 数字
if(isalnum(c)) printf("字母或数字\n"); // 字母或数字
if(isspace(c)) printf("空白字符\n"); // 空格、制表符等
if(islower(c)) printf("小写字母\n"); // 小写
if(isupper(c)) printf("大写字母\n"); // 大写
// 字符转换
char lower = tolower('A'); // 'a'
char upper = toupper('b'); // 'B'
// 其他函数
isprint(c); // 可打印字符
ispunct(c); // 标点符号
isxdigit(c); // 十六进制数字
iscntrl(c); // 控制字符
四、动态内存分配
4.1 内存管理函数
#include <stdlib.h>
// malloc - 分配内存
int *arr = (int*)malloc(10 * sizeof(int));
if(arr == NULL) {
printf("内存分配失败\n");
exit(1);
}
// calloc - 分配并清零
int *zeros = (int*)calloc(10, sizeof(int));
// 等价于 malloc + memset(0)
// realloc - 重新分配
arr = (int*)realloc(arr, 20 * sizeof(int));
// free - 释放内存
free(arr);
free(zeros);
// 注意:free后指针应设为NULL
arr = NULL;
zeros = NULL;
4.2 常见内存错误
// 1. 内存泄漏
void leak() {
int *p = malloc(100);
// 忘记free(p);
}
// 2. 访问已释放内存
int *p = malloc(sizeof(int));
free(p);
*p = 10; // 错误!
// 3. 双重释放
free(p);
free(p); // 错误!
// 4. 内存越界
int *arr = malloc(10 * sizeof(int));
arr[10] = 100; // 越界访问
// 5. 使用未初始化指针
int *p;
*p = 10; // 错误!
4.3 安全的内存使用
// 1. 总是检查malloc返回值
int *safe_malloc(size_t size) {
void *ptr = malloc(size);
if(ptr == NULL) {
fprintf(stderr, "内存分配失败\n");
exit(EXIT_FAILURE);
}
return ptr;
}
// 2. 使用free后立即设为NULL
void safe_free(void **ptr) {
if(ptr && *ptr) {
free(*ptr);
*ptr = NULL;
}
}
// 3. 使用calloc自动清零
int *arr = calloc(10, sizeof(int));
// 4. 使用memset/memcpy
int src[5] = {1, 2, 3, 4, 5};
int dest[5];
memcpy(dest, src, 5 * sizeof(int));
memset(dest, 0, 5 * sizeof(int));
五、结构体
5.1 结构体基础
// 定义结构体
struct Student {
char name[20];
int age;
float score;
};
// 声明变量
struct Student stu1;
struct Student stu2 = {"张三", 20, 85.5};
// 访问成员
strcpy(stu1.name, "李四");
stu1.age = 21;
stu1.score = 90.0;
// 结构体指针
struct Student *pstu = &stu1;
pstu->age = 22; // 等价于 (*pstu).age = 22;
5.2 结构体高级特性
// 结构体嵌套
struct Date {
int year;
int month;
int day;
};
struct Person {
char name[20];
struct Date birthday; // 嵌套结构体
};
// 结构体数组
struct Student class[50];
class[0].age = 20;
class[1].score = 95.5;
// 结构体大小(内存对齐)
struct Example {
char a; // 1字节
int b; // 4字节
short c; // 2字节
double d; // 8字节
};
// 实际大小可能是24字节(包含填充)
printf("结构体大小: %lu\n", sizeof(struct Example));
5.3 联合体(共用体)
union Data {
int i;
float f;
char str[20];
};
union Data data;
data.i = 10; // 使用int成员
printf("data.i = %d\n", data.i);
data.f = 220.5; // 现在使用float成员
printf("data.f = %.1f\n", data.f);
// data.i的值被覆盖了!
// 联合体大小是最大成员的大小
printf("联合体大小: %lu\n", sizeof(union Data));
六、指针类型总结
| 类型 | 声明 | 含义 | 示例 |
|---|---|---|---|
| 整型指针 | int *p |
指向int的指针 | p = &int_var |
| 字符指针 | char *p |
指向char的指针 | p = str |
| 函数指针 | int (*p)(int,int) |
指向函数的指针 | p = add |
| 数组指针 | int (*p)[10] |
指向数组的指针 | p = &arr |
| 指针数组 | int *arr[10] |
元素是指针的数组 | arr[0] = &var |
| 二级指针 | int **p |
指向指针的指针 | p = &ptr |
| void指针 | void *p |
通用指针 | p = &any_var |
总结:数组是数据的集合,指针是内存地址的表示,字符串是特殊的字符数组。掌握这些内容对于理解C语言的内存模型和高效编程至关重要。