教主带你速通C语言基础知识

初学C语言的感受

C语言的学习对我感受最深的就是知识点碎片化。
这里的碎片化不仅是知识点多,单个知识点例如数据、循环中也有很多零碎的点,有时候一道题里面会有多个大知识点,但是每一个大知识点中可能只会用到其中一小点,但每一道题都不一样,必须对每一个知识点都掌握才能运用并结题,这需要适当的背诵记忆。
但是光记忆有时会混乱,所以也需要反复刷题,有些内容只有在刷题中才能了解到。
以下是目前教主对C语言有关知识的整理,欢迎各位阅读并给出建议,希望能有所帮助。
当然我会一直更新哦!

基本知识

  • C语言代码中基本结构中包括:
  1. 预处理指令:以 # 开头,用于在编译前处理代码,常见的有库函数还有宏定义
  2. 全局变量声明:在所有函数外部定义的变量,作用域为整个程序,从声明处到文件结束
  3. 函数声明:即自定义函数,在下文会有讲解
  4. 主函数:C 程序的入口点,每个可执行程序必须有且仅有一个 main 函数,程序从 main 开始执行
  • 关键字和标识符
  1. 关键字:
    关键字是 C 语言预先定义好的、具有特殊含义的单词,用于表示语言的语法结构、数据类型、控制逻辑等。它们是编译器可以直接识别的 “保留字”,不能被用作变量名、函数名或其他自定义名称。
    不用刻意去记这些关键字,在刷题做题时会一直用到,用得多了自然就能记住。
  2. 标识符:
    标识符是你自己为变量、函数、数组、结构体等自定义的名称,用于标识这些程序元素。
    注意:
    标识符由字母(A-Z、a-z)、数字(0-9)、下划线(_)组成。
    必须以字母或下划线开头,不能以数字开头,大小写不同,标识符就不同。
    不能与关键字重名。
    长度无严格限制,建议简洁易懂。
  • 语言
    在C语言代码中所有符号文字都为英文(输出除外),一定要注意好语言,符号错误可能不容易判断。

变量与常量

###变量

  1. 定义:
    变量是程序运行过程中值可以被修改的存储空间,需要先定义后使用。
    定义格式为:数据类型 变量名(可同时初始化:数据类型 变量名 = 初始值)
  2. 作用:
    存储临时数据
    参与程序中的运算与判断
  3. 示例:
#include<stdio.h>
float a;
char c;
long long b;
int a=0;

###常量

  1. 定义:
    常量是程序运行过程中值不可被修改的量,分为直接常量(字面量)和符号常量。
  2. 作用:
    表示固定不变的值

库函数与头文件

库函数

库函数是预先实现好的、可复用的函数,大多由编译器自身提供,存储在 “库文件” 中,也可由自己在库文件中自行定义,通常放在对应的头文件中声明。
简单来说就是编译器本身自带的无需自己在源文件中再定义的函数,可通过特定字符常量直接引用。

头文件

头文件是扩展名为 .h 的文本文件,主要用于声明函数、变量、宏、结构体、枚举等标识符,相当于给编译器和其他源文件提供 说明,告诉它们这些标识符的存在及格式。
也就是告诉编译器你的源文件中可以直接使用这个头文件中包含的库函数。
如:

include<stdio.h>
include<math.h>

等等。
还有一个万能头,它基本包含了所有常用的库函数,并且可以兼容C语言和C++。

include<bits/stdc++.h>
using namespace std;

数据类型

友情提示:数据类型是很容易忽略但是很重要的一个点,它在代码中的作用远远超乎你的想象(至少超过了我的,哈哈)。

整型

数据类型 关键名 所占字节 所占位数 取值范围
短整型 short (int) 2字节 16位 -2¹⁵ ~ 2¹⁵-1
基本整型 int 4字节(现系统) 32位 2³¹ ~ 2³¹-1
长整型 long (int) 4 字节(32 位系统)/ 8 字节(64 位系统) 32 位 / 64 位 32 位:-2³¹ ~ 2³¹-1 64 位:-2⁶³ ~ 2⁶³-1
长长整型 long long 8字节 64位 -2⁶³ ~ 2⁶³-1
无符号短整型 unsigned short(int) 2字节 16位 0 ~ 2¹⁶-1
无符号基本整型 unsigned int 4字节(现系统) 32位 0 ~ 2³²-1
无符号长整型 unsigned long (int) 4 字节(32 位系统)/ 8 字节(64 位系统) 32 位 / 64 位 32 位:0 ~ 2³²-1 64 位:0 ~ 2⁶⁴-1
无符号长长整型 unsigned long long 8字节 64位 0 ~ 2⁶⁴-1

注:八进制输入以0为开头数字;十六进制输入以0x为开头前缀

浮点型

数据类型 关键名 所占字节 精度 取值范围
单精度浮点型 float 4字节 6~7位 1.175494351×10⁻³⁸ ~ 3.402823466×10³⁸
双精度浮点型 double 8字节 15~17位 2.2250738585072014×10⁻³⁰⁸ ~ 1.7976931348623157×10³⁰⁸

字符型

数据类型 关键名 所占字节 精度 取值范围(ASCII 码)
字符型 char 1字节 8位 -128~127

数据类型转换

自动转换

当不同类型的数据参与运算时,会先自动转换为同一类型,取范围较大的类型,具体方式如下:
char/short → int → unsigned int → long → unsigned long → long long → float → double → long double
注:赋值时的转换
赋值时,右侧的值会自动转换为左侧变量的类型(可能导致精度丢失,称为 “降级转换”)。
这个问题一定要注意,可能导致编码运行结果错误。

强制转换

形式为:(目标类型) 表达式
强制转换不会改变原变量的类型和值,只会生成一个新的、转换后的临时值。
优先级问题:强制转换的优先级高于算术运算符,需注意括号位置。

(int)(a + b)  // 先计算a+b,再转换为int
(int)a + b    // 先将a转换为int,再与b运算

宏常量和const常量

两者都是在代码中一般不改变的用字符表示的常量,许多名称都与实际意义有关(如pi…)

宏常量

宏常量是通过预处理指令#define定义的标识符,在预处理阶段会被替换为对应的值,属于 “文本替换” 机制。
无类型:宏常量没有数据类型,不占内存,在预处理替换后消失,将宏名替换为对应值,编译器不检查类型是否匹配。

定义格式(无分号):

#define 宏名 常量值
const常量

const常量是通过const关键字修饰的变量,在编译阶段被处理,本质是 “只读变量”(有类型、有存储地址)。

定义格式(有分号):

const 数据类型 常量名 = 初始值; 

总结:大多数情况优先使用const常量,更安全。

数组

定义

定义格式:
数据类型 数组名[元素个数];

  • 数据类型:数组中元素的数据类型
  • 数组名:标识符,代表数组的首地址
  • 元素个数:正整数(常量或常量表达式),表示数组可存储的元素数量

初始化

数组可以在定义时直接赋值,未明确赋值的元素会被自动初始化(数值型为 0,字符型为’\0’,全局数组默认初始化,部分数组未初始化则值随机)

  • 完全初始化:手工给每一个元素赋值
int arr1[3] = {10, 20, 30};`
  • 部分初始化:只给前 n 个元素赋值,剩余元素自动为 0
int arr2[5] = {1, 2};  // arr2[0]=1, arr2[1]=2, arr2[2]~arr2[4]均为0
  • 省略元素个数:编译器会根据初始化元素数量自动计算数组长度
int arr3[] = {5, 6, 7};  // 等价于 int arr3[3] = {5,6,7}
  • 字符数组初始化
char str1[] = "hello";  // 等价于 char str1[]={'h','e','l','l','o','\0'},长度为6,(预留'\0'的位置)

访问与修改

数组元素通过下标访问,下标从0开始(第一个元素下标为 0最后一个为n-1,n为元素个数)。
示例:
在a[10]中数组第一个元素为a[0],最后一个为a[9],但定义时数组a[n]中的n表示的是元素个数

数组的内存

数组名代表数组的首地址,所以在scanf函数中使用时不用加&

多维数组

  1. 二维数组(最常用)
    定义格式:
    数据类型 数组名[行数][列数];
    示例:
//定义
int m[2][3];  // 2行3列的二维数组,共6个元素
// 初始化
int m1[2][3] = {{1,2,3}, {4,5,6}};  // 按行初始化
int m2[2][3] = {1,2,3,4,5,6};       // 按顺序初始化(效果同上)
int m3[][3] = {{1,2}, {3}};       // 可省略行数(编译器自动计算),列数不能省
  1. 三维数组
    三维数组可以理解为 “数组的数组的数组”,是二维数组的扩展,用于存储具有三个维度的数据(如 “层 × 行 × 列” 的立体结构)。它的定义、初始化和访问方式与二维数组类似,但多了一个维度。用的不是很多,原理还是二维数组,了解即可。

注意:
3. 下标越界:访问超出数组范围的下标(如 arr[5] 访问 int arr[5] 的元素)会导致内存越界,可能修改其他变量的值或崩溃。所以建议定义时候n的值比期待值略大一些。
4. 字符数组是存储字符的数组,不一定以’\0’结尾。
字符串是以’\0’结尾的字符数组,可使用strlen、strcpy等字符串函数处理。
两者要区分。
5. 数组在循环中往往很常用,一定要学会两者结合。

两个最常用的函数

对于C语言初学者而言,这两个函数一定是目前最常用的,别问我是怎么知道的,哈哈哈。

scanf

这是C语言初学阶段最常用的输入函数,一般格式为scanf(“占位符”,&变量名);“ ”中的占位符要与输入时的值相符可以输入一个也可以是多个,但是要一一对应。

scanf("%d%d%f", &a, &b, &f);

注意:

  • 除字符串数组外,scanf 的参数必须加 &(取变量地址),否则无法正确存储数据
  • scanf 中 \n 的作用:跳过空白字符,使用时要注意。
  • scanf函数执行一般用空格和换行符来区分程序运行后输入的量

printf

这是C语言初学阶段最常用的输出函数,一般格式为printf(“占位符”,变量名);“ ”中的占位符要与输入时的值相符,可以输入一个也可以是多个,但是要一一对应。

 printf("a=%d, b=%d, f=%.1f\n", a, b, f);

注意:

  • printf函数中“ ”中的空格会输出,与scanf不同
  • 当输出值为字符型且有具体小数点要求时,在站位符前加.1(数字即可)例如:printf("%.2lf",a);
  • printf函数中常用转义字符:
字符 意义
\n 换行(将光标移到下一行开头)
\t 水平制表符(Tab,相当于按一次 Tab 键)
\r 回车(将光标移到当前行开头,覆盖后续字符)
\\ 输出反斜杠 \(本身是转义符,需转义)
\" 输出双引号 "(避免被解析为字符串结束)
\’ 输出单引号 '(用于字符常量转义)
%% 输出百分号

打印格式

scanf 函数之所以需要占位符(格式控制符),核心原因是为了让函数明确 “要读取什么类型的数据” 以及 “如何解析输入的字符流”。C 语言是强类型语言,不同数据类型(如整数、字符、浮点数)的存储方式和解析规则完全不同,占位符正是解决这一问题的关键。
printf 函数需要使用占位符(如 %d、%s、%f 等),本质是因为 C 语言是强类型语言,且 printf 是一个可变参数函数(可以接收任意数量、任意类型的参数),占位符的作用是告诉编译器如何解析后续参数的类型和格式,确保数据能被正确打印。

数据类型 占位符
short int %hd
int %d
long %ld
long long %lld
float %f
double %lf
double(科学计数法) %e / %E
char %c
字符串 %s
unsigned int %u
unsigned int(八进制) %o
unsigned int(十六进制) %x / %X

运算符

算数运算符

符号 意义
+ 正号(加)
- 负号(减)
*
/ 除(整数类型为整除)
% 取模(余数)
a++ 先返回当前a再加一(先执行再加1)
++a 先加一再返回a(先加1再返回a)
a– 先返回当前a再减一(先执行再减1)
++a 先减一再返回a(先减1再返回a)

赋值运算符

符号 意义
= 赋值
+= 加等于
-= 减等于
*= 乘等于
/= 除等于
%= 模等于

比较运算符

符号 意义
== 等于
!= 不等于
< 小于
> 大于
<= 小于等于
>= 大于等于

逻辑运算符

符号 意义
非(不是)
&& 与(并且)
||

选择分支结构

if语句

if(条件表达式){
	//表达式为真是执行的语句
}
else if(条件表达式){
	//表达式为真是执行的语句
}
...
else(条件表达式){
	//表达式为真是执行的语句
}

注意点:

  1. 用if语句时第一个if语句必须有,之后的else if和else 语句可根据需要来判断,可有可无,并不必须。
  2. 当if后的执行语句只有一行时可以不加{},其余情况都需加{}来保证执行正确。
  3. 条件表达式本质上是回归1或0,如果表达式成立返回1,反之返回0,用bool函数也可以,条件为真即为1或true,反之就是0或false。
  4. if语句中能够再次嵌套if语句,但是要注意逻辑严密以及括号配对正确。

示例(判断素数的个数):

#include<bits/stdc++.h>
using namespace std;
bool sushu(int x){
    if(x<=1) return 0;
    else if(x==2) return 1;
    else{
            for(int i=2;i*i<=x;i++){
            if(x%i==0) return 0;
        }
        return 1;
    }
}

因为教主我已经定义了判断素数的bool类型函数sushu,因此可以在if后直接用函数来判断为真或假:

int main(){
    int x,y;
    scanf("%d %d",&x,&y);
    int s=min(x,y),e=max(x,y);
    int sum=0;
    for(int i=s;i<=e;i++){
        if(sushu(i)){
            sum++;
        }
    }
    printf("%d",sum);
    return 0;
}

**当有多个if多个else语句时会采用就近原则,就和英语中的就近原则一样,每个if会和离自己最近的else语句配对。

switch语句

switch (表达式) {
    case 常量1:
         当表达式的值等于值1时执行的代码
        break;  
    case 常量2:
         当表达式的值等于值2时执行的代码
        break;
    ... 
    default:
        当表达式的值与所有case值都不匹配时执行的代码(可选)
        break;
}

注意:

  1. break用于跳出switch结构,可选,不写则代码继续执行下一个case后的表达式一直到结束。
  2. case后的值一定是常量,不能是变量。
  3. 当想实现case后的值是a或b都行时,采用如下代码:
case a:
case b:表达式
		break

或者

case a:case b:表达式
		break

总结:

  1. 如果条件判断表达式不一样且要进行比较等操作时,优先用if语句
  2. 如果条件判断表达式一样且结果为常量是,优先使用switch语句

循环语句

for语句

for(初始表达式;循环条件;循环操作){
	循环条件为真时重复执行的语句块
}

while语句

while(循环条件表达式){
	循环条件为真时重复执行的语句块;
	通常为更新条件的语句;
}

do while语句

do{
	循环体:先执行的代码;
	更新条件的语句;
}while(循环条件)

补充

  1. 写完每段代码后要检查是否为死循环,防止编译器崩溃
  2. 当循环的执行次数是明确的,可预先确定的时,推荐用 for 循环
  3. 当循环的执行次数不明确,循环是否继续取决于某个动态条件时,推荐用while循环
  4. 当代码至少执行一次且循环的执行次数不明确时,推荐用do while循环
  5. 在主代码中每一个大循环内可用重复相同变量(如i,j…),但在一个大循环内的小循环不能用相同变量
  • break:跳出循环
  • continue:跳过当前循环后面语句,继续下一个循环
  • 这两个词很重要,在循环语句中有时是必须的,非常常用
  • 这两个词不仅在循环语句中经常使用,而且在选择分支中也很常用!
    例如:
    在 洛谷B2141 确定进制题目中:
#include<bits/stdc++.h>
using namespace std;
long long jinzhi(int x,int y){
    long long z=0,power=1,a;
    while(x!=0){
        a=x%10;
        if (a >= y) {
            return -1;  
        }
        z += a * power;
        power *= y;
        x/=10;
    }
    return z;
}
int main(){
    int a,b,c;
    int sum=0;
    scanf("%d%d%d",&a,&b,&c);
    for(int i=2;i<=16;i++){
        if (jinzhi(a,i)!= -1 &&jinzhi(b,i)!= -1 &&jinzhi(c,i)!= -1) { 
            if(jinzhi(a,i)*jinzhi(b,i)==jinzhi(c,i)){
                printf("%d",i);
                sum=1;
                break;
            }
        }
    }
    if(sum!=1){
        printf("0");
    }
    return 0;
}

教主我先定义了用于转换进制的函数(灾后面会有提及),接着在使用时在if语句中加入break,直接跳出循环,即找到最小的符合题意的进制后直接输出。

定义函数

定义函数是在main函数之前自行定义的函数,可在后续主代码中用对应函数名直接使用达到定义函数所对应的效果,但要保证于C语言中关键词不一样。
具体格式:

返回值类型  函数名(输入参数){
	函数体(用于实现逻辑)
	[return 表达式;]
}

注意:

  1. 返回值类型
  • 若不为void,那么在自定义函数最后或中间必须要有return+返回值的格式,以表示函数最终的输出结果
  • 若为void,那么可以省略return
  1. 函数命名规则
  • 由字母、数字、下划线组成,首字符不能是数字,区分大小写
  • 不能是关键字
  1. 在输入参数中要定义类型与变量名如(int x)就是说明函数后输入的是整形变量,可以是多个,变量名只在定义函数时有效,在主代码中运用时不一定是该变量名(全域变量除外)。
  2. 函数中嵌套自身可以实现循环
    例如:
    在 洛谷B2144 阿克曼函数中
int acm(int x,int y){
    if(x==0){
        return y+1;
    }
    else if(x>0&&y==0){
        return acm(x-1,1);
    }
    else{
        int a=acm(x,y-1);
        return acm(x-1,a);
    }
}

这里的定义就是嵌套自身实现循环直至符合函数条件
5. 函数中允许嵌套已经定义过的函数,包括库函数及在该函数前定义的函数

常用数学库函数

在编代码时会用到不少数学中基础函数,在数学库函数中大多都有,以下是一些常用的数学库函数(数学库函数中输入和输出大多都是double 类型)。

函数名 关键字
平方根函数 sqrt(x)
绝对值函数 fabs(x)
幂函数 pow(x,y)
指数函数(以e为底) exp(x)
ln函数 sqrt(x)
lg函数 sqrt(x)
向上取整函数 ceil(x)
向下取整函数 floor(x)
正弦函数 sin(x)
余弦函数 cos(x)
正切函数 tan(x)
反正弦函数 asin(x)
反余弦函数 acos(x)
反正切函数 atan(x)

字符类输入输出

字符的输入和输出

字符类输入和输出比其余的类型要复杂很多,所以这里单独列了出来帮助理解。

  1. 输入:
    scanf(“%c”, &ch):通过%c读取单个字符到变量ch中(注意&)。
    getchar():从键盘读取单个字符(包括空格、回车等),返回读取的字符(int类型,需强转为char)。
    注意:输入时缓冲区可能残留换行符(\n),导致后续读取异常,需手动清空(如用getchar()吸收)。
  2. 输入:
    putchar(c):向屏幕输出单个字符c(c可以是字符变量或字符常量)。
    printf(“%c”, c):通过格式控制符%c输出字符c。

示例:

#include <stdio.h>
int main() {
	char ch1, ch2;
    ch1 = getchar();   
    getchar();         // 吸收输入后的换行符
    printf("再输入一个字符:");
    scanf("%c", &ch2);
    putchar(ch1);       
    putchar('\n');     // 输出换行符
    printf("%c\n", ch2); 
    return 0;
}

字符串的输入和输出

  1. 输入:
    scanf(“%s”, str):读取字符串时,以空格、制表符、换行符为分隔符(即遇到空格就停止),自动在末尾加\0。
    注意:str是数组名(本身表示地址),无需加&,需确保输入长度不超过数组大小。
    fgets(str, n, stdin)(推荐):从stdin(键盘)读取字符串,最多读取n-1个字符(预留 1 个位置存\0),遇到换行符\n会停止并保留\n在字符串中,最后自动加\0。

  2. 输出:
    puts(str):输出字符串str,并自动换行(输出到\0为止)。
    printf(“%s”, str):通过%s输出字符串str,不会自动换行,同样以\0为结束标志。

示例:

#include <stdio.h>
int main() {
    char str1[20], str2[20];
    
    // 使用scanf("%s"):遇到空格停止
    scanf("%s", str1); // 若输入"hello world",str1仅存"hello"
    printf("%s\n", str1);
    
    // 清空缓冲区残留的" world\n"
    while (getchar() != '\n');
    
    // 使用fgets:读取整行(含空格和换行符)
    fgets(str2, 20, stdin); // 最多读19个字符,保留换行符
    printf("%s", str2); // 无需手动加换行(已包含\n)
    return 0;
}

字符串数组的输入和输出

定义形式:char 数组名[行数][每行长度]

  1. 输入字符串数组:
    通过循环遍历每个元素,用scanf(“%s”,数组名)或fgets()输入(注意控制长度)。
  2. 输出字符串数组:
    通过循环遍历每个元素,用puts()或printf(“%s”,数组名)输出。
    示例(输入并输出三个名字):
#include <stdio.h>
int main() {
    char names[3][20]; // 存储3个字符串,每个最长19字符
    
    // 输入3个名字
    for (int i = 0; i < 3; i++) {
        scanf("%s", names[i]); // names[i]是第i个字符串的地址
    }
    
    // 输出3个名字
    printf("你输入的名字是:\n");
    for (int i = 0; i < 3; i++) {
        puts(names[i]);
    }
    return 0;
}

或者:

#include <stdio.h>
#include <string.h> // 用于strcspn函数移除换行符

int main() {
    char names[3][20]; // 存储3个字符串,每个最长19字符
    
    // 输入3个名字(支持带空格的名字)
    for (int i = 0; i < 3; i++) {
        // fgets参数:存储地址、最大长度(含\0)、输入源(stdin表示键盘)
        fgets(names[i], 20, stdin);
        // 移除fgets保留的换行符(若存在)
        // strcspn计算字符串中第一个出现'\n'的位置
        names[i][strcspn(names[i], "\n")] = '\0';
    }
    
    // 输出3个名字(puts自动换行)
    printf("你输入的名字是:\n");
    for (int i = 0; i < 3; i++) {
        puts(names[i]);
    }
    return 0;
}

总结

类型 推荐输入函数 输出函数 注意点
单个字符 getchar(%c) putchar(%c) 注意缓冲区残留的换行符
字符串 fgets(str, n, stdin) puts(%s) \0是结束标志,fgets需指定长度
字符串数组 循环 +fgets() 循环 +puts(%s) 每个元素是字符串,注意数组维度和输入长度

注意:

  1. 在 C 语言中,标准输入输出(如stdio.h中的printf、scanf、fgets等函数)默认使用缓冲区,需要加getchar()来吸收其它内容以及\n 换行符
    如:
// 清空缓冲区残留的" world\n"
    while (getchar() != '\n');

其中循环调用 getchar(),逐个读取缓冲区中残留的字符(、w、o、r、l、d、\n),直到读到 \n 时循环结束,缓冲区被完全清空。

  1. 使用fgets函数时要清除fgets读取时保留的换行符\n,确保字符串仅包含用户输入的有效内容,避免后续输出时出现多余空行。
    如:
		fgets(names[i], 20, stdin);
        // 移除fgets保留的换行符(若存在)
        // strcspn计算字符串中第一个出现'\n'的位置
        names[i][strcspn(names[i], "\n")] = '\0';

字符类常用函数

函数名 作用
strcpy(dest,src) 将src复制到dest
strcat(dest,src) 将src拼接到dest末尾
strcmp(s1,s2) 按 ASCII 码值逐个比较两个字符串的字符,返回一个整数表示比较结果
strch(s,c) 查找c在s中首次出现的位置(都是单个字符)
strlen(str) 计算从起始位置到打一个‘\0’的长度,不包含 ‘\0’ 本身

注:strcmp(s1,s2)比较规则
从两个字符串的第一个字符开始逐个比较(按 ASCII 码值大小)。
一旦遇到不相同的字符,就根据该字符的 ASCII 码值大小决定结果,不再比较后续字符。
如果所有字符都相同,且同时到达 \0(字符串结束标志),则认为两字符串相等。

指针和结构体

指针

指针是一种变量,但它存储的不是普通数据(如整数、字符),而是另一个变量的内存地址。通过指针可以间接访问或修改它所指向的变量的值,这是 C 语言灵活操作内存的基础。
定义格式:*数据类型 指针名;
其中:*是解引用符,通过指针访问某一变量的值,&是取地址符,将变量的内存地址赋值给指针。
示例:

#include <stdio.h>
int main() {
    int a = 10;       // 普通变量a,值为10
    int *p;           // 定义int类型的指针p(指向int变量)
    p = &a;           // &是取地址符,将a的内存地址赋值给p(p指向a)
    
    printf("a的值:%d\n", a);       // 直接访问a:10
    printf("a的地址:%p\n", &a);    // 输出a的地址(如0x7ffd3b3c)
    printf("p存储的地址:%p\n", p); // p存储的是a的地址(与上一行相同)
    printf("p指向的值:%d\n", *p);  // *是解引用符,通过p访问a的值:10
    
    *p = 20;          // 通过指针修改a的值
    printf("修改后a的值:%d\n", a); // 输出20
    return 0;
}

在对于大型数据(如结构体),传递指针通过间接访问比传递整个数据更节省内存和时间。
注意:上述代码中p存储的是a的地址,只有在p前加*才能解引用,访问到a的值

结构体

结构体是一种自定义数据类型,用于将多个不同类型的变量(成员)组合成一个整体,以表示一个 “复杂对象”。

定义

定义格式:

struct 结构体名 {
    数据类型 成员1;
    数据类型 成员2;
    // ... 更多成员
};

示例:

struct Student {
    char name[20];  // 姓名(字符串)
    int age;        // 年龄(整数)
    float score;    // 成绩(浮点数)
};

这里就定义了一个名为Student的结构体

赋值

普通结构体变量的赋值可以通过逐个成员赋值或整体赋值两种方式实现,具体取决于不同赋值场景:

  1. 逐个成员赋值
  • 对结构体的每个成员单独赋值,需使用 . 操作符访问成员。
  • 对于基本类型成员(如 int、float),直接用 = 赋值。
  • 对于字符串成员(char 数组),需用 strcpy 函数(不能直接用 =,因为数组名是常量地址)。
    示例:
#include <stdio.h>
#include <string.h>  // 需包含strcpy函数的头文件

// 定义结构体
struct Student {
    char name[20];  // 字符串成员
    int age;        // 基本类型成员
    float score;    // 基本类型成员
};

int main() {
    struct Student s;  // 定义普通结构体变量
    
    // 逐个成员赋值
    strcpy(s.name, "张三");  // 字符串成员用strcpy赋值
    s.age = 18;             // 基本类型直接赋值
    s.score = 95.5f;        // 基本类型直接赋值
    
    // 输出验证
    printf("姓名:%s,年龄:%d,成绩:%.1f\n", s.name, s.age, s.score);
    return 0;
}
  1. 整体赋值(仅限初始化或同类型结构体间)
    (1)定义时初始化
    在定义结构体变量的同时,可以用大括号 {} 按成员顺序整体赋值(类似数组初始化)。
    示例:
struct Student s = {"李四", 19, 92.0f};  // 定义时整体初始化

(2)同类型结构体变量间整体赋值
两个相同类型的结构体变量,可以直接用 = 整体赋值(编译器会自动复制所有成员)。
示例:

#include <stdio.h>
#include <string.h>

struct Student {
    char name[20];
    int age;
    float score;
};

int main() {
    struct Student s1 = {"王五", 20, 88.5f};
    struct Student s2;  // 定义另一个同类型结构体变量
    
    s2 = s1;  // 整体赋值:将s1的所有成员复制到s2
    
    // 验证s2的值(与s1相同)
    printf("s2:%s,%d,%.1f\n", s2.name, s2.age, s2.score);  // 王五,20,88.5
    return 0;
}
访问

普通结构体变量访问成员需要使用 .(点操作符),
格式为:
结构体变量名.成员名
示例:

#include <string.h>

// 定义结构体类型
struct Book {
    char title[50];  // 书名
    float price;     // 价格
    int pages;       // 页数
};

int main() {
    // 定义普通结构体变量
    struct Book b;
    
    // 1. 给成员赋值(使用 . 操作符)
    strcpy(b.title, "C语言入门");  // 字符串赋值需用strcpy
    b.price = 39.9f;
    b.pages = 300;
    
    // 2. 访问成员并输出(使用 . 操作符)
    printf("书名:%s\n", b.title);   // 输出:书名:C语言入门
    printf("价格:%.1f元\n", b.price); // 输出:价格:39.9元
    printf("页数:%d页\n", b.pages);  // 输出:页数:300页
    
    // 3. 修改成员的值
    b.price = 29.9f;
    printf("打折后价格:%.1f元\n", b.price);  // 输出:打折后价格:29.9元
    
    return 0;
}

结构体指针

结构体指针是指向结构体变量的指针,它存储的是结构体变量的内存地址。通过结构体指针可以间接访问或修改结构体中的成员,这在处理复杂数据结构时非常高效(尤其是传递大型结构体时,传递指针比传递整个结构体更节省内存和时间)。
定义格式:
struct 结构体名 *指针名;
定义完后最关键的操作就是访问结构体成员,这里介绍两种方法:

  1. 先通过 * 解引用得到结构体变量,再用 . 访问成员((*指针名).成员名)
    示例:
    在上述Student结构体中
	struct Student s = {"张三", 18, 92.5};
	struct Student *p = &s;  // &s 是结构体变量s的地址
    printf("姓名:%s\n", (*p).name); 

这里就是用了(*p).name的方式访问了结构体s中的name值

  1. 直接用 -> 操作符(专为结构体指针设计,更简洁):指针名->成员名
    示例:
    还是在上述Student结构体中
	struct Student s = {"张三", 18, 92.5};
	struct Student *p = &s;  // &s 是结构体变量s的地址
	printf("成绩:%.1f\n", p->score); 

这里就是用了 p->score的方式访问了结构体s中的score值

结语

用C语言编写代码时要注意语言,大小写,可能某一个细节上会纠结你很久,都是正常现象,它很讲究细节,后续我还会写关于机算计基础知识的博客,记得要看,记得要看,记得要看。还是建议学了知识要刷题,不然手会生疏哦。
哈哈,这里先声明,例如上述指针和结构体还有堆栈等内容教主我用的不多,待我用熟练后会将这部分缺失的内容补上的。关于上述所有内容,希望能对大家有所帮助,如果有疑惑或者错误的地方,欢迎来和我探讨。
最后祝每一个程序员都不会秃头。
记得点赞关注收藏,做到这三点的都是彭于晏(女生也一样帅)哦。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐