文章目录
- 入门C语言基础
- 1 基础语法
- 1.1 整形
- 1.2 浮点型
- 1.3 常量
- 1.4 运算符
- 1.4.1 算数运算符
- 1.4.2 关系运算
- 1.4.3 逻辑运算
- 1.4.4 赋值运算符
- 1.4.5 其他
- 1.5 if判断
- 1.6 循环
- 1.7 函数
- 1.8 字符串和字符串
- 1.9 数组
- 2 指针
- 2.1 定义指针类型变量和取变量地址
- 2.2 指针类型变量解引用
- 2.3 修改变量的值- 通过指针修改变量值
- 2.4 指针零值和长度
- 2.6 指针类型参数
- 2.7 指针运算(数组的指针)
- 2.8 指针的指针
- 2.9 字符串案例
- 2.9.1 字符串格式
- 2.9.2 判断字符串包含关系
- 2.9.3 字符串相加-复制字符串
- 3 结构体
- 3.1 结构体基本使用
- 3.2 单向链表
- 3.2 双向链表
- 3.3 双向循环链表
- 4 预处理和头文件
- 4.1 预处理
- 4.2 头文件
- 4.2.1 main.c
- 4.2.2 utils.c
- 4.2.3 utils.h
- 4.3 Python源码中头文件的使用
入门C语言基础
1 基础语法
1.1 整形
| 类型 | 存储大小 | 值范围 |
|---|---|---|
| char | 1 字节 | -128 到 127 或 0 到 255 |
| unsigned char | 1 字节 | 0 到 255 |
| signed char | 1 字节 | -128 到 127 |
| int | 2 或 4 字节 | -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647 |
| unsigned int | 2 或 4 字节 | 0 到 65,535 或 0 到 4,294,967,295 |
| short | 2 字节 | -32,768 到 32,767 |
| unsigned short | 2 字节 | 0 到 65,535 |
| long | 4 字节 | -2,147,483,648 到 2,147,483,647 |
| unsigned long | 4 字节 | 0 到 4,294,967,295 |
#include <stdio.h>
int main() {
// 1 整形
char i =127;
printf("%d\n",i);
unsigned char i1=255;
printf("%d\n",i1);
short i2=233;
printf("%d\n",i2);
long i3=123456;
printf("%d\n",i3);
int i4=1234567;
printf("%d\n",i4);
// 查看int类型再我们机器上占几个字节
printf("%d\n",sizeof(i4));
printf("%d\n",sizeof(int));
return 0;
}
1.2 浮点型
| 类型 | 存储大小 | 值范围 | 精度 |
|---|---|---|---|
| float | 4 字节 | 1.2E-38 到 3.4E+38 | 6 位有效位 |
| double | 8 字节 | 2.3E-308 到 1.7E+308 | 15 位有效位 |
| long double | 16 字节 | 3.4E-4932 到 1.1E+4932 | 19 位有效位 |
#include <stdio.h>
int main() {
// 1 浮点型
printf("%d\n",sizeof(float));
printf("%d\n",sizeof(double));
printf("%d\n",sizeof(long double));
// %f代表一般计数法输出
float f1=1.234567F;
double f2=1.2345678;
printf("%f\n",f1);
printf("%f\n",f2);
return 0;
}
1.3 常量
#include <stdio.h>
int main() {
// 1 常量
const int MAX_VALUE=99;
//MAX_VALUE=199;
printf("%d\n",MAX_VALUE);
return 0;
}
1.4 运算符
1.4.1 算数运算符
A 的值为 10,变量 B 的值为 20
| 运算符 | 描述 | 实例 |
|---|---|---|
| + | 把两个操作数相加 | A + B 将得到 30 |
| - | 从第一个操作数中减去第二个操作数 | A - B 将得到 -10 |
| * | 把两个操作数相乘 | A * B 将得到 200 |
| / | 分子除以分母 | B / A 将得到 2 |
| % | 取模运算符,整除后的余数 | B % A 将得到 0 |
| ++ | 自增运算符,整数值增加 1 | A++ 将得到 11 |
| – | 自减运算符,整数值减少 1 | A-- 将得到 9 |
#include <stdio.h>
int main() {
// 1 算数运算符
int a=10;
int b=20;
int c;
c=a+b;
printf("a+b的结果是:%d",c);
c = a - b;
printf("a-b结果是 %d\n", c);
c = a * b;
printf("a * b 结果是%d\n", c);
c = b / a;
printf("b / a的值是 %d\n", c);
c = 10 % 3;
printf("10 % 3取整除的值是 %d\n", c);
c = a++; // 赋值后再加 1 ,c 为 10,a 为 11
c=++a;
printf("赋值后再加 的值是 %d,a的值为:%d\n", c, a);
c = a--; // 赋值后再减 1 ,c 为 11 ,a 为 10
printf("赋值后再减 1的值是 %d,a的值为:%d\n", c, a);
return 0;
}
1.4.2 关系运算
| 运算符 | 描述 |
|---|---|
| == | 检查两个操作数的值是否相等,如果相等则条件为真。 |
| != | 检查两个操作数的值是否相等,如果不相等则条件为真。 |
| > | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 |
| < | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 |
| >= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 |
| <= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 |
1.4.3 逻辑运算
| 运算符 | 描述 |
|---|---|
| && | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 |
| || | 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 |
| ! | 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 |
#include <stdio.h>
int main() {
int a = 10;
int b = 20;
if (a && b) {
printf("条件为真\n");
}
return 0;
}
1.4.4 赋值运算符
| 运算符 | 描述 |
|---|---|
| = | 简单的赋值运算符,把右边操作数的值赋给左边操作数 |
| += | 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 |
| -= | 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 |
| *= | 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 |
| /= | 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 |
| %= | 求模且赋值运算符,求两个操作数的模赋值给左边操作数 |
| <<= | 左移且赋值运算符 |
| >>= | 右移且赋值运算符 |
| &= | 按位与且赋值运算符 |
#include <stdio.h>
int main() {
int a = 21;
int c=10;
c += a;
printf("%d\n", c);
c *= a;
printf("%d\n", c);
return 0;
}
1.4.5 其他
| 运算符 | 描述 | 实例 |
|---|---|---|
| sizeof() | 返回变量的大小。 | sizeof(a) 将返回 4,其中 a 是整数。 |
| & | 返回变量的地址。 | &a; 将给出变量的实际地址。 |
| * | 指向一个变量。 | *a; 将指向一个变量。 |
| ? : | 条件表达式 | 如果条件为真 ? 则值为 X : 否则值为 Y |
#include <stdio.h>
int main() {
int a = 10;
int b = (a == 1) ? 20 : 30;
printf("b 的值是 %d\n", b);
return 0;
}
printf 占位符
| 占位符 | 描述 |
|---|---|
%d或%i | 有符号十进制整数(int) |
| %u | 无符号十进制整数(unsigned int) |
| %o | 无符号八进制整数 |
%x或%X | 无符号十六进制整数(%x 使用小写字母,%X 使用大写字母) |
| %c | 字符(实际上是一个整数,按 ASCII 值输出)。 |
| %f | 浮点数(double) |
%e 或 %E | 浮点数,指数表示法(%e 使用小写字母 e,%E 使用大写字母 E) |
%g或%G | 浮点数,根据数值的大小自动选择 %f 或 %e(%g 使用小写字母,%G 使用大写字母)。 |
%a或%A | 浮点数,十六进制表示法(%a 使用小写字母,%A 使用大写字母) |
| %p | 指针地址(通常用于 void* 类型 |
| %s | 字符串(char 数组或指针) |
| %n | 不输出任何内容,但会将已写入的字符数存储在提供的 int* 变量中。 |
1.5 if判断
#include <stdio.h>
int main() {
int num;
printf("请输入一个数字:");
scanf("%d",&num);
if(num>90){
printf("优秀");
} else if (num>=60 && num<=90){
printf("及格");
} else{
printf("不及格");
}
return 0;
}
1.6 循环
| 循环类型 | 描述 |
|---|---|
| while 循环 | 当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。 |
| for 循环 | 多次执行一个语句序列,简化管理循环变量的代码。 |
| do…while 循环 | 除了它是在循环主体结尾测试条件外,其他与 while 语句类似。 |
#include <stdio.h>
int main() {
// int num=0;
//while循环
// while (num<10){
// printf("%d\n",num);
// num++;
// }
//do while 循环
// do{
// printf("%d\n",num);
// num++;
// } while (num<10);
// for 循环
// for (int i = 0; i < 10; ++i) {
// printf("%d\n",i);
// }
// 死循环
for (;;) {
printf("死循环");
}
return 0;
}
1.7 函数
#include <stdio.h>
# 返回值类型 函数名 (参数a , 参数b)
int add(int a, int b){
return a+b;
}
int main() {
int res=add(3,4);
printf("%d\n",res);
return 0;
}
1.8 字符串和字符串
# c中没有字符串
# 用字符数组表示字符串
#include <stdio.h>
int main() {
// 定义字符
char a='B';
printf("%c\n",a);
//定义字符串
char s[]="justin";
char s1[]={'j','u','s','t','i','n','\0'};
printf("%s\n",s);
printf("%s\n",s1);
return 0;
}
1.9 数组
# 对于数组来说,内部元素是挨个存放,内存地址相邻。
# 它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,是一系列相同类型的变量
#include <stdio.h>
int main() {
// 定义char类型数组--》使用字符数组表示字符串,但是如果是数组的表示形式,必须加 \0,表示字符串结尾, 支付字符数组需要\0 结尾
char s1[]={'j','s','s','s','s','s','\0'};
// 定义int类型数组
int i1[]={22,33,44,55};
// 数组取值赋值
printf("%d\n",i1[1]);
printf("%c\n",s1[1]);
// 数组大小
printf("%d\n", sizeof(i1)); // 64为机器--》一个int占4个bytes---》数组大小为4--》占16个字节
printf("%d\n", sizeof(i1)/sizeof(i1[0])); // 数组大小为4
char c[]={22,33,44,55};
printf("%d\n", sizeof(c)); // 4个字节
printf("%d\n", sizeof(c[0])); // 一个char类型占1个字节
printf("%d\n", sizeof(c)/sizeof(c[0]));
return 0;
}
2 指针
# 1 指针是存储 变量内存地址的变量
-指针是变量:int,float一样,是变量
-指针是存放 内存地址 0x22123
# 2 下图解释
int v1=666;
int *v2=&v1;

2.1 定义指针类型变量和取变量地址
int main() {
// 定义指针类型变量,取变量地址
int v1=666;
int *v2=&v1;
printf("v2的值是:%p\n",v2); // 000000000061FE14
return 0;
}

2.2 指针类型变量解引用
int main() {
// 定义指针类型变量,取变量地址
int v1=666;
int *v2=&v1;
printf("v2的值是:%p\n",v2); // 000000000061FE14
// int a=*v2;
printf("v2的指向具体的值是:%d\n",*v2);
printf("v1值是:%d\n",v1);
return 0;
}
2.3 修改变量的值- 通过指针修改变量值
#include <stdio.h>
/* 三句重点的话
1 * 放在变量定义前 [ int *v2 ],表示执行 这个类型的指针
2 & 放在变量前 [ &v1 ],表示取这个变量的地址---》赋值给一个指针类型
3 * 放在指针类型变量前, [*v2] 表示解引用--》把地址反解成真正的值
*/
// python中所有类型的对象,都是c语言结构体的指针
int main() {
int v1=666;
int *v2=&v1;
//1 通过值改变量
v1=888;
printf("v1的值是:%d\n",v1);
printf("v2指向具体的值是:%d",*v2); // 888
//2 通过指针修改变量值
*v2=999;
printf("v1的值是:%d\n",v1);
return 0;
}

2.4 指针零值和长度
int main() {
int *v2=NULL; // 如果不赋初值,是错乱的,要么赋值,要么赋为 NULL,表示空指针
// printf("%p\n",v2);
if(!v2){
printf("指针是空的\n");
}
printf("%d\n",sizeof(v2)); // 无论什么类型的指针,都是8,指针这个变量长度是固定的
return 0;
}
下图就是python 变量能够随便更改数据类型的实现。实际上,python中的变量指向的都是同一类型的内存地址,再由内存地址执行不同类型的值

2.6 指针类型参数
#include <stdio.h>
void changeA(int a){
a=999;
printf("内部的a是:%d\n",a);// 999
}
void changPointA(int *p){
*p=666;
printf("内部的p指向的值是:%d\n",*p);// 666
}
int main() {
int a =1;
changeA(a);
//printf("外部的a是:%d\n",a); // 1 c语言函数传参,是复制一份传过去
changPointA(&a); // 复制一份传入---》把地址复制一份传入
printf("外部的a是:%d\n",a); // 666
return 0;
}
2.7 指针运算(数组的指针)
# 1 数组的指针 指针数组
int *p=&[1,2,3,] [*v1,*v2,*v3]

#include <stdio.h>
int main() {
// 定义一个数组
int a[3]={11,22,33};
int *p=&a;
//int *p=&a[0];
printf("p的值是:%p\n",p); // 内存地址:000000000061FE0C 数组第0个位置的
printf("p解引用是:%d\n",*p); // 是数组的第0个位置,11
// 指针运算---》取数组第二个位置的值
printf("数组第二个位置值是:%d\n",a[1]);
// 使用指针运算得到 通过指针的 ++ 就可以依次拿到数组的下一个位置
// printf("使用指针运算得到数组第二个位置值:%d\n",*p++);
// printf("使用指针运算得到数组第二个位置值:%d\n",*p);
// printf("使用指针运算得到数组第二个位置值:%d\n",*(++p));
// printf("使用指针运算得到数组第二个位置值:%d\n",*(++p));
// printf("使用指针运算得到数组第三个位置值:%d\n",*(p+2));
return 0;
}
在C语言中,无论是通过指针还是通过数组下标来访问数组元素,如果访问超出了数组定义的边界(例如,对于定义为a[3]的数组,尝试访问a[4]),这种行为是未定义的(undefined behavior)。尽管在某些情况下,程序可能会在内存中“拿到”一个值(这通常是因为内存布局的原因,使得越界访问没有立即导致程序崩溃),但这并不意味着这个值是有效的或安全的。这种越界访问实际上会导致内存溢出(或称为内存越界),它可能引发各种不可预测的问题,包括数据损坏、程序崩溃或安全漏洞。
2.8 指针的指针
int main() {
int a = 100;
int *p1 = &a;
int **p2 = &p1;
int ***p3 = &p2;
printf("p3的值是:%p\n", p3); //000000000061FE00
printf("p3指向的值是(其实是p2的值):%p\n", *p3); //000000000061FE08
printf("p2的值是:%p\n", p2); //000000000061FE08
printf("通过p3拿到真正的a的值:%d\n", ***p3);
return 0;
}
2.9 字符串案例
2.9.1 字符串格式
#include <stdio.h>
int main() {
// 1 定义一个char数组
char a[6];
char *p =&a;
//等同于下面
// a[0]='j';
// a[1]='u';
// a[2]='s';
// a[3]='t';
// a[4]='i';
// a[5]='n';
//字符串格式
sprintf(p,"%c",'j');
p+=1;
sprintf(p,"%c",'u');
p+=1;
sprintf(p,"%c",'s');
p+=1;
sprintf(p,"%c",'t');
p+=1;
sprintf(p,"%c",'i');
p+=1;
sprintf(p,"%c",'n');
printf("a的值:%s\n",a);
return 0;
}
2.9.2 判断字符串包含关系
#include <stdio.h>
#include <string.h>
int main() {
char name[]="justin is handsome";
//判断handsome是否在字符串中
char *res=strstr(name,"handsome");
if(res){
printf("存在");
} else{
printf("不存在");
}
return 0;
}
2.9.3 字符串相加-复制字符串
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
char name[]="justin";
char role[]="teacher";
// 把上述俩字符串放到一个字符串中
// 分配内存空间
char *s=malloc(strlen(name)+ strlen(role)+1);
strcpy(s,name);//字符串复制,把name内容复制到s指针上
strcat(s,role); // 字符串拼接,把s指针后面追加role字符串
printf("%s\n",s);
return 0;
}
3 结构体
# 一种用户自定义的数据类型,它允许存储不同类型的数据项
相当于类,放了一堆属性
# 结构体中的数据成员可以是基本数据类型(如 int、float、char 等),也可以是其他结构体类型、指针类型等
3.1 结构体基本使用
#include <stdio.h>
// 1定义结构体 Person
struct Person{
char name[30];
int age;
};
int main() {
// 1 使用结构体
struct Person p1={"justin",19};
printf("人名是:%s,年龄是:%d\n",p1.name,p1.age);
//2 结构体指针
struct Person *p2=&p1;
// printf("人名是:%s,年龄是:%d\n",(*p2).name,(*p2).age); // 这个一般不用
printf("人名是:%s,年龄是:%d\n",p2->name,p2->age); // 用这个
return 0;
}
3.2 单向链表

#include <stdio.h>
struct Node{
int data;
struct Node *next;
};
int main() {
struct Node v3={33};
struct Node v2={22,&v3};
struct Node v1={11,&v2};
printf("v1的数据是:%d\n",v1.data);
printf("v1的下一个节点的数据(v2的data):%d\n",v1.next->data);
printf("v1的下一个节点的下一个节点数据(v2的data):%d\n",v1.next->next->data);
return 0;
}
3.2 双向链表

#include <stdio.h>
struct Node{
int data;
struct Node * prev;
struct Node *next;
};
int main() {
struct Node v3={33};
struct Node v2={22};
struct Node v1={11};
v1.next=&v2;
v2.next=&v3;
v2.prev=&v1;
v3.prev=&v2;
printf("v1的值是:%d\n",v1.data);
printf("通过v1拿到v2的值是:%d\n",v1.next->data);
printf("通过v1拿到v3的值是:%d\n",v1.next->next->data);
printf("v3的值是:%d\n",v3.data);
printf("通过v3拿到v2的值是:%d\n",v1.prev->data);
printf("通过v3拿到v1的值是:%d\n",v1.prev->prev->data);
return 0;
}
3.3 双向循环链表

#include <stdio.h>
struct Node{
int data;
struct Node * prev;
struct Node *next;
};
int main() {
struct Node v3={33};
struct Node v2={22};
struct Node v1={11};
v1.next=&v2;
v1.prev = &v3
v2.next=&v3;
v2.prev=&v1;
v3.prev=&v2;
v3.next=&v1;
printf("v1的值是:%d\n",v1.data);
printf("通过v1拿到v2的值是:%d\n",v1.next->data);
printf("通过v1拿到v3的值是:%d\n",v1.next->next->next->next->data);
printf("v3的值是:%d\n",v3.data);
printf("通过v3拿到v2的值是:%d\n",v1.prev->data);
printf("通过v3拿到v1的值是:%d\n",v1.prev->prev->data);
return 0;
}
4 预处理和头文件
4.1 预处理
# 预处理,在程序编译之前会先运行的。
#include <stdio.h>
# define ME 200
# define SIZE 18
int main() {
// SIZE=99;// 程序运行过程中改就不行了
printf("%d\n",ME); // 只能用
return 0;
}
4.2 头文件
项目目录
├── main.c 主文件
├── utils.c 些函数方法
└── utils.h 用来注册能被导出的函数
4.2.1 main.c
#include <stdio.h>
#include "utils.h" // 内置的用 <> ,自定义的用 ""
int main() {
int res=add(4,5);
printf("%d\n",res);
}
4.2.2 utils.c
int add(int a, int b) {
return a + b + 100;
}
int a(){
return 11;
}
4.2.3 utils.h
int add(int a, int b);
4.3 Python源码中头文件的使用
#1 cpython解释器是用c写的--》里面都是c代码
https://www.python.org/downloads/source/
# 2 python中所有数据类型,都是c语言结构体指针
# github上cpython源码:https://github.com/python/cpython
#Objects 下会有咱们得列表,字典等结构体
https://github.com/python/cpython/tree/main/Objects#
# 列表:的源代码
https://github.com/python/cpython/blob/main/Objects/listobject.c
# 列表的头文件
https://github.com/python/cpython/blob/main/Include/listobject.h



















