C语言核心概念复习(二)

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语言的内存模型和高效编程至关重要。

相关推荐
觉醒大王1 小时前
硕士/博士研究生避坑指南
笔记·深度学习·学习·自然语言处理·职场和发展·学习方法
Gain_chance2 小时前
31-学习笔记尚硅谷数仓搭建-DWD层工具域优惠券使用(支付)、互动域收藏商品、流量域页面浏览、用户域用户注册、用户域用户登录事务事实表建表语句及分析
笔记·学习
冰暮流星2 小时前
javascript之双重循环
开发语言·前端·javascript
墨月白2 小时前
[QT]QProcess的相关使用
android·开发语言·qt
小小码农Come on2 小时前
QT信号槽机制原理
开发语言·qt
XX風2 小时前
2.1_binary_search_tree
算法·计算机视觉
KoiHeng2 小时前
Java的文件知识与IO操作
java·开发语言
不想写bug呀2 小时前
买卖股票问题
算法·买卖股票问题
-Try hard-2 小时前
完全二叉树、非完全二叉树、哈希表的创建与遍历
开发语言·算法·vim·散列表