文章目录
- 入门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



















