day10 10.2 C语言基础之基础语法

发布时间 2023-08-02 12:15:12作者: Chimengmeng

【一】整形

类型 存储大小 值范围
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() {
    char i=99;
    printf("%d\n",i);
    printf("%c\n",i);

    unsigned char i1=99;
    printf("%d\n",i1);
    printf("%c\n",i1);
    //short i=32767;  // 默认有符号,整数最大32767
    //signed short i=32767;  // 默认有符号,整数最大32767
    unsigned short i1=65535;  // 有符号,正数最大 65535

    //int i2=65536;
    long i3=65536;
    printf("%d",i3);
    
    // 各种类型的存储大小与系统位数有关,但目前通用的以64位系统为主
    // int 在64位机器上,占4字节
    int i9=9999;
    printf("%d\n",sizeof(i9));
    printf("%d\n",sizeof(int));
    return 0;
}
#include <stdio.h>

int main() {
    // 定义整型 变量

    // 【1】 char 类型
    char a = 'j';
    char a1 = 99;
    // (1)显示字符
    printf("a的值是:%c\n", a);
    // a的值是:j -- 字符打印出来是字符
    printf("a的值是:%c\n", a1);
    // a的值是:c -- 数字被ASCII码强转成字符
    // (2)显示数字
    printf("a的值是:%d\n", a);
    // a的值是:106 -- 字符被ASCII码强转成数字
    printf("a的值是:%d\n", a1);
    // a的值是:99 -- 数字打印出来就是数字

    // 【2】int类型
    int age = 9;
    printf("age的值是:%d\n", age);
    // age的值是:9
    age = 999;
    printf("age的值是:%d\n", age);
    // age的值是:999 -- 可以被直接修改

    // 【3】 无符号 int 类型
    unsigned int price = 88;
    printf("price:%d\n", price);
    // price:88

    // 【4】 查看这个类型占几个字节 -- int 分平台 long 不分平台
    printf("price占 %d 个字节\n", sizeof(price));
    // price占 4 个字节
    printf("int类型占 %d 个字节\n", sizeof(int));
    // int类型占 4 个字节
    printf("short类型占 %d 个字节\n", sizeof(short));
    // short类型占节 2 个字
    printf("long类型占 %d 个字节\n", sizeof(long));
    // long类型占 4 个字节

    // 【5】 short 有符号 - 有正负 - 正负2的15次方-1
    // -32768 ~ 32768
    short s = 32769;
    printf("short: %d \n",s);
    // short: -32767

    return 0;
}

【二】浮点型

类型 存储大小 值范围 精度
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() {
    printf("float 存储最大字节数 : %d \n", sizeof(float));
    printf("double 存储最大字节数 : %d \n", sizeof(double));
    printf("long double 存储最大字节数 : %d \n", sizeof(long double));
    // %f代表一般计数法输出,%e代表指数计数法输出
    float f1=365.123456789F;
    printf("%f---%e\n",f1,f1);

    double f2=365.123456789;
    printf("%f---%e\n",f2,f2);

    long double f3=365.123456789L;
    printf("%Lf---%Le\n",f3,f3);

    return 0;
}
#include <stdio.h>

int main() {
    // 【1】 浮点型
    printf("float占字节数:%d \n", sizeof(float));
    // float占字节数:4
    printf("double 占字节数:%d \n", sizeof(double));
    // double 占字节数:8
    printf("long double 占字节数:%d \n", sizeof(long double));
    // long double 占字节数:16

    // 【2】 如果是float类型 结尾可以加 f或F
    float f = 365.123456789f;
    float f1 = 365.123456789F;
    float f2 = 365.123456789;
    printf("f 的值是:%f\n", f);
    // f 的值是:365.123444
    printf("f1 的值是:%f\n", f1);
    // f1 的值是:365.123444
    printf("f2 的值是:%f\n", f2);
    // f2 的值是:365.123444

    double f3 = 365.123456789;
    printf("f3 的值是:%f\n", f3);
    // f3 的值是:365.123457

    // 如果是long double ,字符串格式化需要使用 %Lf
    long double f4 = 365.123456789;
    printf("f4 的值是:%Lf\n", f4);
    // f4 的值是:365.123457

    // 强类型语言,不同类型之间不允许直接运算,但是Java/C可以,因为他们有隐式类型转换
    // Python是动态强类型语言
    // Java是静态强类型语言
    f4 = f2 + f3;
    printf("f5 的值是:%Lf\n", f4);
    // f5 的值是:730.246900

    return 0;
}

【三】常量

#include <stdio.h>
int main() {
    const int MAX_VALUE =99;
    //MAX_VALUE =199;
    printf("%d\n",MAX_VALUE );
    return 0;
}
#include <stdio.h>

int main() {
    // 常量
    const int MAX_VALUE = 999;
    MAX_VALUE = 888;
    printf("MAX_VALUE ::>>", MAX_VALUE)
    //  assignment of read-only variable 'MAX_VALUE'

    return 0;
}

【四】 运算符

【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() {
    int a = 10;
    int b = 20;
    int c;

    c = a + b;
    printf("a+b结果是 %d\n", 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
    printf("赋值后再加 的值是 %d,a的值为:%d\n", c, a);
    c = a--;  // 赋值后再减 1 ,c 为 11 ,a 为 10
    printf("赋值后再减 1的值是 %d,a的值为:%d\n", c, a);
    return 0;
}
// a++ 与 ++a 的区别
#include <stdio.h>

int main() {
    // 算数运算符
    int a = 20;
    int b = 40;
    int c = 60;
    int d; // 定义了,没有初始化,默认不是 0
    printf("a是:> %d,b是:> %d,c是:> %d,d是:> %d", a, b, c, d);
    // a是:> 20,b是:> 40,c是:> 60,d是:> -1781264384
    d = a + b;
    printf("a + b 运算值:>>%d \n", d); // a + b 运算值:>>60

    d = a - b;
    printf("a - b 运算值:>>%d \n", d); // a - b 运算值:>>-20

    d = a * b;
    printf("a * b 运算值:>>%d \n", d); // a * b 运算值:>>800

    d = a / b;
    printf("a / b 运算值:>>%d \n", d); // a / b 运算值:>>0

    d = a % b;
    printf("a % b 运算值:>>%d \n", d); // a % b 运算值:>>20

    // a++ 先执行代码,再 +1 ,打印出来是 20 ,但是 a 已经变成了 21
    d = a++;
    printf("a++ 运算值:>>%d \n", d); // a++ 运算值:>>20

    // ++a 先计算 +1 ,再执行代码,打印出来是 21,a 是21
    d = ++a;
    printf("++a 运算值:>>%d \n", d); // ++a 运算值:>>21

    return 0;
}

【2】关系运算符

运算符 描述
== 检查两个操作数的值是否相等,如果相等则条件为真。
!= 检查两个操作数的值是否相等,如果不相等则条件为真。
> 检查左操作数的值是否大于右操作数的值,如果是则条件为真。
< 检查左操作数的值是否小于右操作数的值,如果是则条件为真。
>= 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。
<= 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。
#include <stdio.h>

int main() {
    // 逻辑运算符
    int a = 10;
    int b = 20;
    if (a == b) {
        printf("等于");
    } else {
        printf("不等于");
    };
    // 不等于

    return 0;
}

【3】逻辑运算符

运算符 描述
&& 称为逻辑与运算符。如果两个操作数都非零,则条件为真。
|| 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。
! 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。
#include <stdio.h>
#include <stdbool.h>

int main() {
    bool isTrue = true;
    int a = 10;
    int b = 20;
    if (a && b) {
        printf("条件为真\n");
    }
    return 0;
}
#include <stdio.h>

int main() {
    // 逻辑运算符 && 如果两个操作数都非零,则条件为真。
    if (10 && 11) {
        printf("代码执行");
    };
    // 代码执行

    // 逻辑运算符 ! 如果两个操作数中有任意一个非零,则条件为真
    if(!10){
        printf("代码执行\n");
    }
    // 代码执行

    if(!0){
        printf("代码执行\n");
    }
    // 代码执行

    return 0;
}

【4】赋值运算符

运算符 描述
= 简单的赋值运算符,把右边操作数的值赋给左边操作数
+= 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数
-= 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数
*= 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数
/= 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数
%= 求模且赋值运算符,求两个操作数的模赋值给左边操作数
<<= 左移且赋值运算符
>>= 右移且赋值运算符
&= 按位与且赋值运算符
#include <stdio.h>
int main() {
    int a = 21;
    int c;
    c += a;
    printf("%d\n", c);
    c *= a;
    printf("%d\n", c);
    return 0;
}

【5】其它

运算符 描述 实例
sizeof() 返回变量的大小。 sizeof(a) 将返回 4,其中 a 是整数。
& 返回变量的地址。 &a; 将给出变量的实际地址。
* 指向一个变量。 *a; 将指向一个变量。
? : 条件表达式 如果条件为真 ? 则值为 X : 否则值为 Y
#include <stdio.h>
#include <stdbool.h>

int main() {
    int a = 4;

    printf("%d\n", sizeof(a));


    int *ptr = &a;
    printf("a 的值是 %d,ptr的值是%p\n", a, ptr);

    a = 10;
    int b = (a == 1) ? 20 : 30;
    printf("b 的值是 %d\n", b);

    return 0;
}

【五】if 判断

#include <stdio.h>
#include <stdbool.h>

int main() {

    int num;
    printf("输入一个数字 : ");
    // 从控制台接受输入
    scanf("%d", &num); // & : 取地址符号,否则拿不到值
    if (num > 90) {
        printf("优秀");
    } else if (num > 60 && num < 90) {
        printf("及格");
    } else {
        printf("不及格");
    }

    return 0;
}

【六】循环

循环类型 描述
while 循环 当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。
for 循环 多次执行一个语句序列,简化管理循环变量的代码。
do...while 循环 除了它是在循环主体结尾测试条件外,其他与 while 语句类似。
#include <stdio.h>
#include <stdbool.h>

int main() {

    int num = 0;
    // while循环
    while (num<10){
        printf("%d\n",num);
        num++;
    }
    return 0;
}
#include <stdio.h>
#include <stdbool.h>

int main() {

    int num = 0;

    //do while 循环
    do {
        printf("%d\n", num);
        num++;
    } while (num < 10);

    return 0;
}
#include <stdio.h>
#include <stdbool.h>

int main() {

    int num = 0;


    // for 循环
    for (int i = 0; i < 10; ++i) {
        printf("%d\n", i);
    }

    return 0;
}
#include <stdio.h>
#include <stdbool.h>

int main() {

    int num = 0;
    //死循环
    for (;  ; ) {
        printf("我是死循环");
    };
    return 0;
}

【七】函数

返回值类型 函数名( 参数类型 形参 )
{
  函数体;
}
#include <stdio.h>


int add(int a, int b) {
    return a + b;
}

int main() {

    int res = add(10, 10);
    printf("%d", res);
    return 0;
}

// 20

【八】字符和字符串

  • 在C语言中没有字符串。

  • 字符数组 创造出字符串出来(每个字符占1个字节)。

#include <stdio.h>

int main() {
    // 定义字符
    char a = 'a';
    printf("%c\n", a);
    // 定字符串 - 字符数组表示字符串
    // 字符串结尾必须加 \0
     char s1[]= {'j','u','s','t','i','n','\0'};
    // 简写
    char s[]="justin";
    printf("%s\n", s);
    printf("%s\n", s1);

    return 0;
}

【1】字符串格式

#include <stdio.h>

int main() {

    char a[6];  // c中没有字符串,字符串数组表示字符串
    char *p =&a; // 定义了一个指针p,指向了a数组的第一个位置

    sprintf(p,"%c",'j');   //['j','','','','','']
//    ++p;
    p=p+1; // 把p移动到下一个位置
    sprintf(p,"%c",'u');   //['j','u','','','','']

    p=p+1; // 把p移动到下一个位置
    sprintf(p,"%c",'s');   //['j','u','s','','','']

    p=p+1; // 把p移动到下一个位置
    sprintf(p,"%c",'t');   //['j','u','s','t','','']

    p=p+1; // 把p移动到下一个位置
    sprintf(p,"%c",'i');   //['j','u','s','t','i','']

    p=p+1; // 把p移动到下一个位置
    sprintf(p,"%c",'n');   //['j','u','s','t','i','n']

    printf("%s\n",a);


}

【2】判断字符串包含

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

int main() {

    char name[] = "dream is handsome";

    // 判断子字符串是否在name中
    char *res = strstr(name, "is");
    if (!res) {
        printf("不在");
    } else {
        printf("is 在name中,%p\n", res);  //res是个地址,是is这个子字符串起始位置的地址
        // is 在name中,00000062e47ff606
    }

    printf("%c\n", *res);
    // i
    printf("%c\n", *(++res));
    // s
}

【3】字符串复制和拼接(相加)

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

int main() {

    char name[] = "dream";
    char role[] = "baby";
    // 把两个字符串相加----》申请一片内存空间,char类型数组,大小是 name的长度+role的长度
    char *res = malloc(strlen(name) + strlen(role) + 1);
    // 把name的内容复制到 res中
    strcpy(res, name);
    // 继续字符串拼接
    char *aa = strcat(res, role);
    printf(res);
    // dreambaby
    printf("\n");
    printf("%c", *aa);
    // d
}

【九】数组

  • 对于数组来说,内部元素是挨个存放,内存地址相邻。

  • 它可以存储一个固定大小的相同类型元素的顺序集合。

  • 数组是用来存储一系列数据,是一系列相同类型的变量

char v2[]= {'j','u','s','t','i','n','\0'};
int v3[3] = {11,22,33};
  • 元素固定
  • 类型固定(每个元素在内存中占据长度相同)
#include <stdio.h>

int main() {
    // 【1】字符串的内存地址
    char v3[] = "justin";

    printf("第0个位置值:%c,内存地址:%p \n", v3[0], &v3[0]);
    // 第0个位置值:j,内存地址:0000008bdc1ff989
    printf("第1个位置值:%c,内存地址:%p \n", v3[1], &v3[1]);
    // 第1个位置值:u,内存地址:0000008bdc1ff98a
    printf("第2个位置值:%c,内存地址:%p \n", v3[2], &v3[2]);
    // 第2个位置值:s,内存地址:0000008bdc1ff98b

    // 【2】数组的定义和取值
    //int a[3];  定义没有初始化
    //int a[3]={1,2,3}; // 定义并初始化
    int a[] = {1, 2, 3}; // 定义并初始化,大小可以不填,但是定义完后,大小也固定
    printf("%d\n", a[0]);
    // 1
    printf("%d\n", a[1]);
    // 2
    printf("%d\n", a[2]);
    // 3
    // 数组中没有索引对应的值,也会返回一个值
    // 如果数组定义是 3 但是修改了 4 对应的值 -- 对应在内存空间就是指针指向了这个地址
    // 如果不调用,问题不大,调用就会报错
    printf("%d\n", a[3]);
    // 1937074688

    // 数组大小 --- 计算数组长度
    // 数组长度 = 字节长度 / 每个元素的大小(可以是数组索引为0/数组类型)
    printf("%d", sizeof(a) / sizeof(a[0]));
    // 3
    printf("%d", sizeof(a) / sizeof(int));
    // 3

    return 0;
}
# include <stdio.h>

int main() {
    int v3[] = {11, 22, 33, 44, 55, 66};  // 每个整型4字节
    printf("第0个位置值:%d,内存地址:%p \n", v3[0], &v3[0]); // 值所在的内存地址 0x00000
    printf("第1个位置值:%d,内存地址:%p \n", v3[1], &v3[1]); // 0x00004
    printf("第2个位置值:%d,内存地址:%p \n", v3[2], &v3[2]); // 0x00008
    return 0;
}

【十】指针

  • 指针就是个变量

  • 指针是存储变量内存地址的变量

/*
 【1】如果看到 类型 * 变量   ----> 定义指针类型的变量,表示指针指向这个类型的指针
 【2】如果看到 &变量        ----> 取这个变量的地址
 【3】如果看到 *变量        ----> 解引用,将指针指向真正的值
 */
#include <stdio.h>

int main() {
    int a=10;
    // 【1】定义一个指针类型变量,指向a
    int *point =&a;
    printf("%d\n",a);
    // 10

    // 【2】通过地址取到a
    printf("%d\n",*point);
    // 10

    // 【3】就想看地址是啥:16进制表示
    printf("%p\n",point);
    // 00000077bc5ffc54

    printf("%p\n",&a);
    // 00000077bc5ffc54
}

【1】定义指针类型变量(类型 * 变量)和取变量地址(&变量)

#include <stdio.h>
int main() {
    int v1 = 666;
    int *v2 = &v1;   // & 取地址符,得到是 指针类型(64位操作系统,8字节)
    printf("v2的值为:%p", v2);

    return 0;
}

【2】指针类型变量解引用(*变量)

#include <stdio.h>


int main() {
    int v1 = 666;
    int *v2 = &v1;   // & 取地址符,得到是 指针类型(64位操作系统,8字节)
    printf("v2的值为:%p\n", v2);
    printf("v2解引用后:%d\n",*v2);
    return 0;
}

【3】修改变量的值

#include <stdio.h>


int main() {
    int v1 = 666;
    int *v2 = &v1;   // & 取地址符,得到是 指针类型(64位操作系统,8字节)
    printf("v2的值为:%p\n", v2);
    printf("v2解引用后:%d\n",*v2);

    // 修改v1的值,查看v2
    v1 = 999;
    printf("v1的值为:%p\n", v1);
    printf("v2的值为:%p\n", v2);
    printf("v2解引用后:%d\n",*v2);

    return 0;
}

【4】指针的零值和长度

  • 长度:8bytes
  • 零值:NULL
#include <stdio.h>


int main() {
    //int *v2;   // & 取地址符,得到是 指针类型(64位操作系统,8字节)
    //char *v2;   // & 取地址符,得到是 指针类型(64位操作系统,8字节)
    //long *v2;   // & 取地址符,得到是 指针类型(64位操作系统,8字节)
    long *v2 = NULL;   // & 取地址符,得到是 指针类型(64位操作系统,8字节)
    printf("%p\n", v2);
    printf("%d\n", sizeof(v2));
    if (!v2) {
        printf("指针为空");
    }

    return 0;
}

#include <stdio.h>


int main() {
    // 定义指针,没有赋初值
    // int *p;
    // 定义指针,没有赋初值,等同于上面
    int *p = NULL;
    printf("%p\n", p);
    // 0000000000000000
    // 并不是占  2个字节,所有指针类型都占8个字节

    printf("%d\n", sizeof(p));
    // 8个字节,指针都是占8个字节

    int a = 100;
    p = &a;  // 指针赋值
    printf("%d\n", *p);
    // 100 
    printf("%p\n", p);
    // 00000004671ff7d4
    printf("%d\n", sizeof(p));
    // 8

}

【5】通过指针修改原变量值

#include <stdio.h>


int main() {
    int a=10;
    int *v2 = &a;
    *v2=99;
    printf("a的值为:%d\n",a);
    printf("v2的值为:%p\n",v2);

    return 0;
}

#include <stdio.h>


int main() {
    int a = 999;
    int *p = &a;
    printf("%d\n", a);
    // 999
    printf("%p\n", p);
    // 000000042d9ffbd4

    // 改a的值
    a = 88;
    printf("%d\n", a);
    // 88
    printf("%p\n", p);
    // 000000042d9ffbd4

    // 使用指针改值
    // 指针解引用再改值
    (*p) = 666;
    // (*p) = 666 ---> a=666;
    printf("%d\n", a);
    // 666
    printf("%p\n", p);
    //没有变化 - 000000042d9ffbd4
}

【6】指针类型参数

#include <stdio.h>

void changPointA(int *p) {
    *p = 99;
}

void changA(int a) {
    a = 99;
}

int main() {
    int a = 10;
    int *v2 = &a;
    printf("v2的值为:%p\n", v2);
//    changPointA(v2); //传递到函数中,a的值被改变了
    changA(a); //传递到函数中,a的值没有被改变
    printf("v2的值为:%p\n", v2);
    printf("a的值为:%d\n", a);


    return 0;
}

【7】数组的指针(指针运算)

#include <stdio.h>

int main() {
    int a[3] = {11, 22, 33};
    int *p = &a;
    printf("数组的指针值为:%p\n", p);
    printf("取数组第0个元素方式一:%d\n",a[0]);
    printf("取数组第0个元素方式二:%d\n",*p);

    printf("取数组第1个元素方式一:%d\n",a[1]);
    printf("取数组第1个元素方式二:%d\n",*(++p));


    return 0;
}


// 指针数组和数组指针

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

int main() {

    int a[3] = {11, 22, 33};
    int *p = &a;
    printf("数组的指针:%p\n", p);



    int i = 10;
    int x = 20;
    int y = 30;
    int *p1[] = {&i, &x, &y};
    printf("指针数组:%d", **p1);


    return 0;
}

#include <stdio.h>

int main() {
    // 指针运算 ----> 数组的指针
    int a[3] = {11, 22, 33};
    // 定义一个指向数组的指针 --- 指向数组的第一个位置
    int *p = &a;
    // 指针进行运算
    printf("%p\n", p);
    // 000000f6947ff72c
    printf("%d\n", *p);
    // 解引用 - 11

    // 【1】通过指针位置改
    a[1] = 88;
    printf("%d\n", a[1]);
    // 88

    // 【2】通过指针运算
    // 指针运算,由于p指向了数组的第一个位置
    // ++p,表示移动指针到了数组的第2 个位置
    // 解引用后,把值改为77了
    *(++p) = 77;
    printf("%d\n", a[1]);
    // 77

    // 【3】指针基于上述,现在移动到了索引为 2 的位置
    printf("%d\n", *(++p));
    // 33

}

数组的指针和指针数组

#include <stdio.h>

int main() {

    int a[3] = {11, 22, 33};
    // 指向数组的指针
    int *p = &a;

    // 数组中放的都是指针类型
    int x = 10;
    int y = 100;
    int z = 100;
	
    // 指针数组:数组中放的都是指针类型
    int *p1[] = {&x, &y, &z};
    printf("%p\n", p1[0]);
    // 0000009a2bfff8c8
    printf("%d", *(p1[0]));
    // 10 -- 对内存地址解引用
}

【8】指针的指针

#include <stdio.h>
int main() {

    int a = 100;
    int *p1 = &a;
    int **p2 = &p1;
    int ***p3 = &p2;
    printf("p3的值为:%p\n", p3);
    printf("p2的值为:%p\n", *p3);
    printf("p1的值为:%p\n", **p3);
    printf("a的值为:%d\n", ***p3);


    return 0;
}

#include <stdio.h>

int main() {
    int a = 10;
    // 定义一个指针类型变量,指向a
    int *point = &a;
    // 指针的指针
    int **p1 = &point;
    printf("%p\n", p1);
    // 00000059d89ffdb8
	
    printf("%p\n", *p1);
    // 00000059d89ffdc4
	
    // *p1 <---> point
    printf("%p\n", point);
    // 00000059d89ffdc4
    
    // 解引用
    printf("%d\n", **p1);
    // 10   
}

【9】字符串案例

(1)字符串格式化

#include <stdio.h>

int main() {

    char a[6];
    char *p = &a;
    // 格式化
    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("值为:%s", a);


    return 0;
}

#include <stdio.h>

int main() {

    // c中没有字符串,字符串数组表示字符串
    char a[6];
    // 定义了一个指针p,指向了a数组的第一个位置
    char *p = &a;

    // 【1】 向指针数组的第0个位置放入一个字符
    sprintf(p, "%c", 'j');   //['j','','','','','']

    // 【2】把p移动到下一个位置
    // ++p;
    p = p + 1; // 等价于上面 -- 只能写一种
    sprintf(p, "%c", 'u');   //['j','u','','','','']

    // 【3】把p移动到下一个位置
    p = p + 1;
    sprintf(p, "%c", 's');   //['j','u','s','','','']

    // 【4】把p移动到下一个位置
    p = p + 1;
    sprintf(p, "%c", 't');   //['j','u','s','t','','']

    // 【5】把p移动到下一个位置
    p = p + 1;
    sprintf(p, "%c", 'i');   //['j','u','s','t','i','']

    // 【6】把p移动到下一个位置
    p = p + 1;
    sprintf(p, "%c", 'n');   //['j','u','s','t','i','n']

    printf("%s\n", a);
    // justin
}

(2)判断字符串包含关系

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

int main() {

    char name[] = "justin is handsome";

    // 判断是name中是否存在子序列is
    char *res = strstr(name, "is");
    if (!res) {
        printf("不存在");
    } else {
        printf("存在,从位置 %p 匹配成功的\n", res);
    }


    return 0;
}

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

int main() {

    char name[] = "justin is handsome";

    // 判断子字符串是否在name中
    // 返回指针类型的结果 --- > 解引用 ---> 拿到值
    char *res = strstr(name, "is");
    if (!res) {
        printf("不在");
    } else {
        printf("is 在name中,%p\n", res);
        // is 在name中,000000f8483ff7f7
        //res是个地址,是is这个子字符串起始位置的地址
    }

    printf("%c\n", *res);
    // i
    printf("%c\n", *(++res));
    // s  

}

(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);
    return 0;
}

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

int main() {

    char name[] = "justin";
    char role[] = "teacher";
    // 把两个字符串相加----》申请一片内存空间
    // char类型数组,大小是 name的长度+role的长度
    char *res = malloc(strlen(name) + strlen(role) + 1);
    // 把name的内容复制到 res 中
    strcpy(res, name);
    // 继续字符串拼接
    char *aa = strcat(res, role);
    printf(res);
    // justinteacher
    printf("\n");

    printf("%c", *aa);
    // j
}
序号 函数 & 作用
1 strcpy(s1, s2); 复制字符串 s2 到字符串 s1。
2 strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。
3 strlen(s1); 返回字符串 s1 的长度。
4 strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
5 strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
6 strstr(s1, s2); 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。

【十一】结构体s

  • 一种用户自定义的数据类型,它允许存储不同类型的数据项

  • 结构体中的数据成员可以是基本数据类型(如 int、float、char 等),也可以是其他结构体类型、指针类型等

【1】基本使用

  • 定义结构体
    • 结构体中的数据成员可以是基本数据类型(如 int、float、char 等)
    • 也可以是其他结构体类型、指针类型等
// 定义结构体
struct person{
    char name[10];
    int age;
    animal aa;
    char *gender;
}
  • 基本使用
#include <stdio.h>
#include <string.h>
#include<stdlib.h>

// 定义结构体
struct Person {
    char name[30];
    int age;
};

int main() {
    // 使用结构体
    struct Person p1 = {"dream", 18};
    struct Person p2 = {"蚩梦",};
    struct Person p3 = {"小梦梦", 18};
    printf("p1的名字为:%s\n", p1.name);
    // p1的名字为:dream
    printf("p2的名字为:%s\n", p2.name);

    // p2的名字为:蚩梦
    printf("p2的年龄为:%d\n", p2.age);
    // p2的年龄为:0  // 默认值为 0

    printf("p3的年龄为:%d\n", p1.age);
    // p3的年龄为:18

    // 结构体指针 解引用
    // 类型 * 变量   ----> 定义指针类型的变量,表示指针指向这个类型的指针
    // 如果看到 &变量        ----> 取这个变量的地址
    // 如果看到 *变量        ----> 解引用,将指针指向真正的值
    // 首先定义了一个指向Person结构体的指针变量pointPerson,并将其初始化为指向p3的地址。
    // 然后使用*pointPerson进行解引用操作,获取指针指向的结构体对象,再通过点号运算符访问成员变量。
    struct Person *pointPerson = &p3;
    printf("p3的年龄为:%d\n", (*pointPerson).age);
    // p3的年龄为:18
    printf("p3的姓名为:%s\n", pointPerson->name);
    // p3的姓名为:小梦梦

    return 0;
}

【2】单向链表

#include <stdio.h>

struct Node {
    int data; // 可以存储数字
    struct Node *next;
};

int main() {

    struct Node v3 = {33}; // next 没有传 空指针 NULL
    struct Node v2 = {22, &v3};
    struct Node v1 = {11, &v2};

    printf("v1的数据为:%d\n", v1.data);
    // v1的数据为:11

    // 通过 v1 拿到 v2 的data ---> v1.next->data ---> 本质上是 v1.next.data
    printf("v1的下一个元素数据为:%d\n", v1.next->data);
    // v1的下一个元素数据为:22

    // 通过 v1 拿到 v2 的next ----> 通过 v2 拿到 v3 的data
    printf("v1的下一个元素的下一个数据为:%d\n", v1.next->next->data);
    // v1的下一个元素的下一个数据为:33

    return 0;
}

【3】双向链表

#include <stdio.h>

struct Person {
    int data;  // 存放数据
    struct Person *next; // 下一个元素
    struct Person *prev; // 上一个元素
};

int main() {
    struct Person p3 = {33};
    struct Person p2 = {22};
    struct Person p1 = {11};

    // p1的 next 放 p2 的地址
    p1.next = &p2;

    // p2的 next 放 p3 的地址
    p2.next = &p3;
    // p2的 prev 放 p1 的地址
    p2.prev = &p1;

    // p3的 prev 放 p2 的地址
    p3.prev = &p2;

    printf("p1的值: %d\n", p1.data);
    // p1的值: 11
    printf("p2的值: %d\n", p1.next->data);
    // p2的值: 22
    printf("p3的值: %d\n", p1.next->next->data);
    // p3的值: 33

    printf("p3的值: %d\n", p3.data);
    // p3的值: 33
    printf("p2的值: %d\n", p3.prev->data);
    // p2的值: 22
    printf("p1的值: %d\n", p3.prev->prev->data);
    // p1的值: 11
    return 0;
}

【4】双向环状链表

# include <stdio.h>

struct Person {
    int data;
    struct Person *next;
    struct Person *prev;
};

int main() {
    struct Person p3 = {33};
    struct Person p2 = {22};
    struct Person p1 = {11};
    p1.next = &p2;
    p1.prev = &p3;

    p2.next = &p3;
    p2.prev = &p1;

    p3.prev = &p2;
    p3.next = &p1;

    printf("p1的值: %d\n", p1.data);
    // p1的值: 11
    printf("p2的值: %d\n", p1.next->data);
    // p2的值: 22
    printf("p3的值: %d\n", p1.next->next->data);
    // p3的值: 33

    printf("p1的值: %d\n", p1.next->next->next->data);
    // p1的值: 11
    printf("p2的值: %d\n", p1.next->next->next->next->data);
    // p2的值: 22
    printf("p3的值: %d\n", p1.next->next->next->next->next->data);
    // p3的值: 33

    return 0;
}

【十二】预处理和头文件

【1】预处理

  • 预处理,在程序编译之前会先运行的。
# include <stdio.h>

# define ME  200
# define SIZE  18
// 在代码开始,定义了ME常量,和SIZE常量
// 这个代码会在程序编译之前执行

int main() {
    int data = 19;
    //ME=99;  // 是常量,不能修改
    printf("%d-%d-%d \n", ME, SIZE, data);
    // 200-18-19 
    return 0;
}
# include <stdio.h>

// 预处理定义函数
// 函数ADD有两个 参数x1, x2  返回值是 x1+x2+100
# define ADD(x1, x2)(x1+x2+100)
# define ADF(x1, x2)(x1*x2)

int main() {
    int data = ADD(11, 22);
    printf("结果:%d \n", data);
    // 结果:133

    // ***注意***
    int dat = ADF(2, 3); // 执行顺序是 (2*3)
    int res = ADF(2 + 6, 3); // 执行顺序是 (2 + (3*6))
    printf("结果:%d \n", dat);
    // 结果:6 
    printf("结果:%d \n", res);
    // 结果:20
    return 0;
}
# include <stdio.h>

# define DB(x1, x2)(x1*x2)

int main() {
    int data = DB(2 + 1, 4);
    printf("结果:%d \n", data);  // 结果是6,由于先运行,变成了2+1*4
    return 0;
}

【2】头文件

项目目录
├── main.c 	// 主文件
├── utils.c // 自己定义的源文件
└── utils.h // 源文件必须配合头文件
// utils.h
// 类似于Java中的接口,只写了但是没有实现
int add(int a,int b);
// utils.c

int add(int a,int b){
    return a+b;
}
//main.c

# include <stdio.h>
# include "utils.h"

int main() {
    int data = add(100,200);
    printf("结果:%d \n", data);
    return 0;
}
  • 比如后期,我们要进行JNI开发时,我们会在自己的c文件用去引入C语言中提供、JNI中提供一些头文件,才能使用头文件中的功能。

【3】Python源码中头文件的使用

  • Cpython解释器是用C语言写的,所以在Cpython的源码中可以看到很多C代码
  • Cpython源码包
    • https://www.python.org/downloads/source/
  • Cpython源码(github)
    • 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

  • CPython是Python的一种实现,它是使用C语言编写的。
    • CPython包括了Python解释器和标准库。
    • python中的字段/列表,本质上都是C语言结构体的指针
  • 所有的Python都是用C语言写的
    • 这句话是不正确的
    • 并不是所有的Python都是用C语言写的。
    • Python是一种高级编程语言,它可以使用多种语言进行扩展和编写。
    • 除了CPython之外,还有其他实现方式,比如Jython是使用Java编写的,IronPython是使用C#编写的。
    • 此外,许多Python库和框架是用Python本身来编写的
      • 例如Django和Flask。
      • 但是也有一些Python模块是使用C语言编写的,这是为了获得更高的性能或者与底层库进行交互。
  • 如果语言自己写自己,这叫做语言的自举
    • 当一个编程语言的编译器、解释器或运行时环境是用该语言自身来编写时,我们称之为语言的自举。
    • 例如,如果用Python编写一个Python解释器,然后使用该解释器来执行Python代码,这就是Python语言的自举。
    • 语言的自举可以进一步推动该语言的发展和演化,因为开发者可以使用自己编写的编译器或解释器来改进语言本身。