C语言进阶指南:从数组、函数到指针与结构体
C语言作为底层开发的核心语言,其语法简洁但涉及丰富的内存管理概念。本文梳理了C语言第二阶段的几个关键知识点:数组用于存储同类型数据序列,函数实现功能模块化复用,字符串依赖隐式的' '结尾标识。同时深入解析了指针如何作为地址标志操作内存,以及利用struct关键字定义复杂数据模型的方法。
数组与函数封装
当我们需要存储多个相同类型的数据时,可以使用数组(Array)。数组是一组连续内存空间的集合,声明时需指定类型和元素数量。例如:
int arr[10] = {1,2,3,4,5,6,7,8,9,10}; // 定义包含10个整数的数组若初始化时未明确指定长度,编译器会根据花括号内的元素个数自动推断。数组元素的访问依赖于下标(Index),且C语言中下标严格从0开始。
int main()
{
int i = 0;
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for (i = 0; i <10; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
为了提升代码的可读性与复用性,我们可以将特定功能封装为函数(Function)。函数的基本结构包含返回类型、函数名、参数列表及函数体。以下示例演示了一个打印1到10数字的无参函数及其调用:
void Print()
{
for (int i = 0; i < 10; i++)
{
printf("%d ", i+1);
}
}
int main()
{
Print();
return 0;
}
字符串存储原理与内存验证
在C语言中,没有独立的字符串数据类型。字符串是通过双引号括起来的字符序列来定义的,如"hello world"。需要注意的是,C语言的字符串在处理时会自动在末尾添加一个空字符 '\0' 作为结束标志。这意味着"hello world"在内存中实际上占用了12个字节(11个可见字符 + 1个'\0')。
为了验证这一特性,我们可以通过对比不同方式定义的字符数组来观察差异:arr1仅包含普通字符而无结束符,arr2显式添加了'\0',而arr3则直接由字符串常量赋值。
#include <stdio.h>
int main()
{
char arr1[] = { 'a', 'b', 'c', 'd' };
char arr2[] = { 'a', 'b', 'c', 'd','\0' };
char* arr3 = "abcd";
printf("arr1 %s\n", arr1); // 可能引发越界读取,因为缺少\0
printf("arr2 %s\n", arr2);
printf("arr3 %s\n", arr3);
return 0;
}
sizeof 与 strlen 的本质区别
在处理字符串与数组时,sizeof与strlen常被混淆,二者有本质区别:sizeof是关键字,计算的是对象或类型所占用的总字节数;而strlen是标准库函数(头文件<string.h>),专门统计从起始位置到第一个'\0'之前的字符个数。
sizeof 的应用
sizeof可用于计算变量或类型的大小。计算变量时可省略括号,计算类型时必须带括号。如下例所示,即使数组内容相同或未显式分配空间,其大小也由其定义时的初始容量决定。
int main()
{
char arr1[] = { 'a', 'b', 'c', 'd' };
char arr2[] = { 'a', 'b', 'c', 'd', '\0' };
char arr3[10] = { 'a', 'b', 'c', 'd', '\0' };
printf("arr1 %d\n", sizeof(arr1)); // 结果为 4
printf("arr2 %d\n", sizeof(arr2)); // 结果为 5
printf("arr3 %d\n", sizeof(arr3)); // 结果为 10
return 0;
}
对于类型大小的计算:
int main()
{
int a = 0;
printf("int %d\n", sizeof(int));
printf("a %d\n", sizeof(a));
return 0;
}
测试不带括号计算变量:
int main()
{
int a = 0;
printf("a %d\n", sizeof a);
return 0;
}
强制要求类型加括号以避免歧义:
int main()
{
int a = 0;
// printf("int %d\n", sizeof int); // 错误写法,实际编译会报错或警告,标准建议始终带括号
// 正确做法:printf("int %d\n", sizeof(int));
return 0;
}
strlen 的逻辑
>注:此处原文图片展示 strlen 计数逻辑,依赖找到 \0 停止 使用strlen需要引入<string.h>。该函数遍历字符串直到遇到'\0'才结束计数。如果数组中没有'\0',函数将持续向后读取直到碰巧遇到一个值为0的内存单元,这会导致不可预知的结果。
#include <string.h>
int main()
{
char arr1[] = { 'a', 'b', 'c', 'd' };
char arr2[] = { 'a', 'b', 'c', 'd', '\0' };
char* arr3 = "abcd";
printf("arr1 %d\n", strlen(arr1)); // 危险行为,越界搜索\0
printf("arr2 %d\n", strlen(arr2)); // 结果为 4
printf("arr3 %d\n", strlen(arr3)); // 结果为 4
return 0;
}
转义字符的处理机制
当在字符串中包含特殊符号(如目录路径c:\code\test.c)时,反斜杠\会被解释为转义字符的起始符,导致显示异常。此时需要使用双反斜杠\\来表示真实的反斜杠。
常见转义字符包括:
\?:防止三字母词(如??)被解析为])。\\':表示单引号字符。\":表示字符串内部的双引号。\\:表示一个反斜杠。\n,\t,\r等:控制输出格式。\ddd(八进制)与\xdd(十六进制):表示特定ASCII值。
"c:\\test\\328\\test.c",其中的\t会被视为水平制表符,\32被视为八进制转义,最终通过strlen获取的长度并非直观看到的字符数。
基础控制流:选择与循环
C语言的控制流主要通过算数、逻辑、位运算等操作符结合条件判断来实现。其中逗号表达式从左向右依次执行,整个表达式的值是最后一项的值。
if-else 多分支判断
通过输入数值进行决策,是程序交互的基础。支持使用else if扩展判断条件。注意,else总是与距离它最近的未匹配的if配对。
switch-case 离散条件处理
switch适用于基于整数类型(如char, int)的多分支跳转。每个case块结束后通常需接break以跳出循环,否则会发生“穿透”效应,继续执行下一个case的代码直至遇到break或结束。default块处理所有未匹配的情况。
无break导致的穿透现象演示:
循环结构分为三种:
- for循环:适合已知循环次数的场景。先执行初始化,再判断条件,最后执行调整步长。
- while循环:先判断后执行。如果初始条件不满足,循环体一次也不会执行。
- do...while循环:先执行一次循环体,再进行条件判断,至少执行一次。
在循环内部,可使用continue跳过本次剩余代码进入下一轮迭代,或使用break彻底终止当前循环。
指针:内存访问的桥梁
指针是指向内存地址的变量。无论指针指向何种数据类型,其在32位系统中大小为4字节,在64位系统中大小为8字节。这是由系统寻址能力决定的。
通过解引用操作符*,可以访问指针所指向地址处的实际数据。修改解引用的值即等同于修改原变量的值。
int main()
{
int a = 10;
int * p = &a; // p 存的是 a 的地址
printf("a = %d\n", *p); // 读取 a 的值
*p = 20; // 通过指针修改 a 的值
printf("a = %d\n", a);
return 0;
}
尽管指针指向不同类型(如int*, char*, double*, 甚至struct Student*),但只要运行环境固定(如均为64位机器),这些指针变量本身的大小是相同的。
自定义类型 struct 结构体
>注:原文此处标题误写为自定义类型,实为结构体struct的具体应用 当需要描述一组相关但类型不同的属性(如学生的姓名、性别、年龄)时,可使用结构体(Structure)将不同数据类型打包在一起。关键字为struct。
结构体的定义形式:
struct 结构体名字
{
成员类型 成员名;
...
};struct Student
{
char name[100];
char sex[10];
int age;
};
int main()
{
struct Student stu = { "张三", "男", 18};
printf("%s %s %d\n", stu.name, stu.sex, stu.age);
return 0;
}
以上就是C语言核心基础知识的详细内容,更多关于C语言进阶技巧的资料请关注本站其它相关文章!免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









