C语言

main.c  源文件

#include <stdio.h>

/**
 * C语言中#开头的称为预处理指令
    例如:#include #define #if

 * C语言中没有导包的说法
    如果一个函数在多个.c文件中重复使用,那么函数要声明在.h头文件中,
    如果仅在当前文件中使用,可以直接定义该函数。

 * 文件的类型
    xxx.h    头文件, 用于包含函数声明、宏定义、结构体和类的定义等
    xxx.c    实现文件(源文件)

 * 导入的两种形式
    #include <filename>:
        这种形式仅在系统头文件目录中搜索头文件。
        一般用于包含标准库头文件。

    #include "filename":
        这种形式首先在当前目录或源文件所在目录搜索头文件。
        如果在当前目录中找不到指定的头文件,再会在系统头文件目录中搜索。
 */

#include <stdlib.h>

#include <string.h>

// 自定义头文件,声明函数 funcMethod99
#include "HeaderDemo.h"

// 宏常量
#define LEN 10

// 宏函数
#define SQUARE(x) ((x) * (x))

// 定义一个多行宏函数,计算并打印两个数的和
#define PRINT_SUM(a, b) do {            \
    int sum = (a) + (b);                \
    printf("Sum of %d and %d is %d\n", a, b, sum); \
} while(0)


// int aa = 20;    // 报错: multiple definition of `aa'
// 从demo2.c文件引入变量aa, 一个全局变量只能有一处定义
extern int aa;

// 声明函数 void 无返回值
void dataType() {
    // 字符和字符串
    // char字符 (1个字节)
    char c = 'A';
    // cahr数组 (字符长度 + 一个结束符 '\0', 一共占12 个字符 + 1 个结束符, 13个字节)
    char str[] = "Hello, World!";
    size_t length = strlen(str);
    printf("字符: %c\n", c);
    printf("字符串: %s\n", str);
    printf("字符串长度: %zu\n", length);

    // 整数 shor(短整型 2字节)
    // int(整形 4字节)
    // long(长整型通常 4个字节,在某些64位的UNIX系统 8个字节,不常用)
    // long long(长长整型 8个字节)
    int i = 123;
    unsigned int u = 123;
    printf("有符号整数: %d\n", i);
    printf("无符号整数: %u\n", u);
    printf("八进制: %o\n", i);
    printf("十六进制: %x\n", i);

    // 浮点数
    float f = 123.456f;
    double d = 123.456789;
    printf("浮点数: %f\n", f);
    printf("双精度浮点数: %lf\n", d);     // %lf  double标识符
    printf("科学计数法: %e\n", d);

    // 格式控制
    printf("宽度控制: %15f\n", f);    // %15f 宽度为15,不足位数 在前面 空格填充
    printf("精度控制: %.2f\n", f);    // %.2f 保留两位小数
    printf("零填充: %010d\n", i);    // 宽度10,不足位数 在前面 零填充
    printf("左对齐: %-10d\n", i);    // 宽度10,不足位数 在后面 空格填充

    // 指针 得到的是一个十六进制表示的内存地址,它表示变量 i 在内存中的位置
    // &i 前面是 与符号 在这里看着有点抽象
    printf("指针: %p\n", (void *) &i);    // 000000000061FDC4

    // 百分号
    printf("百分号: %%\n");
}

void strFormat() {
    char name[] = "Alice";
    int age = 30;
    float weight = 65.50f;

    // 使用格式化字符串输出变量的值
    printf("Name = %s, Age = %d, Weight = %.1f\n", name, age, weight);

    // 自动类型转换
    float f = 10;
    printf("%.0f\n", f);
}

int batchStatement() {
    int a, b = 50, c = 100;
    // 在 C 语言中,局部变量如果没有被显式初始化,它们的值是未定义的,即它们可能包含任意值。
    printf("%d\n", a);  // 6644585
    printf("%d\n", b);
    printf("%d\n", c);

    return 0;
}

int calculate() {
    // 算术运算符 加+、减-、乘*、除/、取余%

    int a = 5, b = 3;
    float c = a / b;  // 在运算过程中是将两个整数计算,先得到结果整数1,再赋值float,所以是1.00
    printf("%.2f\n", c);    // 1.00


    float d = 5, e = 3;
    float f = d / e;
    printf("%.2f\n", f);    // 1.67

    int j = 100;
    int h = 3;
    double k = j * 1.0 / h;
    printf("%.3f\n", k);

    // i++ 和 ++i
    int i = 1;
//    int u = ++ i;   // 先+1 后赋值 结果2
    int u = i++;   // 先赋值 后+1 结果1
    printf("%d\n", u);

    return 0;
}

void sizeofMethods() {
    // 基本数据类型的大小
    printf("Size of int: %zu bytes\n", sizeof(int));
    printf("Size of float: %zu bytes\n", sizeof(float));
    printf("Size of double: %zu bytes\n", sizeof(double));
    printf("Size of char: %zu bytes\n", sizeof(char));

    // 变量的大小
    int a = 10;
    float b = 20.5f;
    double c = 30.5;
    char d = 'A';

    printf("Size of variable a: %zu bytes\n", sizeof(a));
    printf("Size of variable b: %zu bytes\n", sizeof(b));
    printf("Size of variable c: %zu bytes\n", sizeof(c));
    printf("Size of variable d: %zu bytes\n", sizeof(d));


    // 数组的大小
    int numbers[5] = {1, 2, 3, 4, 5};

    printf("Size of array: %zu bytes\n", sizeof(numbers));
    printf("Size of one element: %zu bytes\n", sizeof(numbers[0]));

    // 数组长度
    printf("Number of elements in the array: %zu\n", sizeof(numbers) / sizeof(numbers[0]));

    // 如果sizeof的参数是一个数据类型名称,那么必须使用括号
    // 如果sizeof的参数是一个变量名或表达式,那么括号是可选的
    printf("Size of int: %zu bytes\n", sizeof(int));
    printf("Size of variable a: %zu bytes\n", sizeof a);
}


void swap(int *a, int *b) {
    *a ^= *b;
    *b ^= *a;
    *a ^= *b;
}

void array() {
    // 字符数组
    char str[] = "Hello, world";
    printf("charArr[0]: %c\n", str[0]);

    // 固定长度int数组
    int numberArr[5] = {1, 2, 3, 4, 5};
    int numberArrLength = sizeof numberArr / sizeof numberArr[0];
    printf("length: %d\n", numberArrLength);
    for (int i = 0; i < numberArrLength; ++i) {
        printf("numberArr: %d\n", numberArr[i]);
    }

    // 指针数组(不指定长度)
    char *strArr[] = {"腾讯", "微信", "QQ", "小马哥"};
    int strArrLength = sizeof strArr / sizeof strArr[0];
    for (int i = 0; i < strArrLength; ++i) {
        printf("strArr: %s\n", strArr[i]);
    }

    // int数组的倒序输出
    int arr[] = {100, 200, 300, 400, 500};
    int arrLength = sizeof arr / sizeof(int);
    for (int i = (arrLength - 1); i >= 0; --i) {
        printf("reverseArr: Index: %d number: %d\n", i, arr[i]);
    }

    // 异或 对数组进行 交换倒序
    for (int i = 0; i < arrLength / 2; ++i) {
        swap(&arr[i], &arr[arrLength - i - 1]);
    }
    for (int i = 0; i < arrLength; ++i) {
        printf("Reversed ^ array: %d\n", arr[i]);
    }

}


void strMethods() {
    char str[] = "Hello, World!";
    size_t length = strlen(str);  // length 为 13
    printf("strlen:计算字符串的长度(不包括终止的空字符 \\0): %zu\n", length);


    char dest[20];
    strcpy(dest, str);
    printf("strcpy:复制字符串: %s\n", dest);


    char dest2[20];
    strncpy(dest2, str, 3);   // 只复制前 3 个字符
    dest2[3] = '\0';    // 手动添加字符串终止符
    printf("strncpy:复制指定长度的字符串: %s\n", dest2);


    char str1[20] = "Hello,";   // 指定长度需要保证,后面拼接进来的字符串有足够的空间使用,可以为空 str1[]
    char str2[] = "World!";
    strcat(str1, str2);  // 将 str2 连接到 str1 后面
    printf("strcat:字符串拼接: %s\n", str1);


    char str3[20] = "Hello, ";
    char str4[] = "World!";
    strncat(str3, str4, 3);  // 只连接 str4 的前 3 个字符
    printf("strncat:连接指定长度的字符串: %s\n", str3);


    char str5[] = "Hello";
    char str6[] = "World";
    int result = strcmp(str5, str6);  // 比较 str5 和 str6
    printf("strcmp:比较字符串,相等返回0,否则-1: %d\n", result);


    char str7[] = "Hello";
    char str8[] = "World";
    int result2 = strncmp(str7, str8, 3);  // 比较 str1 和 str2 的前 3 个字符
    printf("strncmp:比较指定长度的字符串,相等返回0,否则-1: %d\n", result2);


    char str9[] = "Hello";
    char str10[] = "ell";
    char str11[] = "eee";
    char *ptr = strstr(str9, str10);
    char *ptr2 = strstr(str9, str11);
    printf("Hello是否包含ell: %s\n", ptr);
    printf("Hello是否包含eee: %s\n", ptr2);
    if (ptr != NULL) {
        printf("Hello包含ell\n");
    } else {
        printf("Hello不包含ell\n");
    }
}


void macro() {
    printf("宏常量输出数组: ");
    printf("[");
    for (int i = 0; i < LEN; ++i) {
        if (i != 0) {
            printf(",");
        }
        printf("%d", i + 1);
    }
    printf("]\n");

    int num = 5;
    int result = SQUARE(num);
    printf("宏函数求乘积: %d\n", result);


    PRINT_SUM(10, 10);   // 使用宏函数传递字面量值,无需显式地声明它们的类型

}


// 声明函数和定义的两种形式
int funcMethod(int a, int b);

int funcMethod2(char *str) {
    printf("funcMethod2函数传入字符串: %s\n", str);
}

/**
 * static静态变量、函数
    静态局部变量
        声明周期延长,与全局变量的生命周期一致,作用域范围不变
    静态全局变量
        限定在本文件中使用
    静态函数
        限定在本文件中使用
 */

void testStatic() {
    int c = 100;
    static int s = 100;
    c++;
    s++;
    printf("局部变量c: %d\n", c);
    printf("静态局部变量s: %d\n", s);
}

// 静态全局变量,只能在本文件中使用,不能使用extern在其他文件中引入
static int abc = 100;

// 静态函数,只能在本文件中使用
static void testStaticFunc() {
    printf("这是静态函数,不能在其他文件中引用!\n");
}


/**
 * 指针
 *  变量的直接访问和间接访问
 *      直接访问:常规方法对变量进行赋值,取值
 *      间接访问:通过指针变量对指向的变量进行赋值,取值
 *
 *  指针和指针变量的概念
 *      指针就是地址,地址就是指针
 *      指针变量,其实是C语言的一种变量,这种变量比较特殊,通常会被赋值为某变量的地址(p=&a),
 *              然后使用*p的方式去间接访问p所指向的变量
 *
 *  指针变量的定义
 *      int* p:
 *      int *p:
 *      int * p:
 *      三种写法均可,常见的是前两种
 *
 *  通常在64位系统中,int *res、 float *res、 char *res等都是占 8 个字节
 *      32位系统中,这些通常占 4 个字节
 *
 *  指针中的运算符
 *      &   取地址符    加在变量前
 *      *   指针符号    使用指针时, *p代表指针变量p所指向的变量,这个过程叫解引用
 *
 */

void testFunc(int n, float *n2, char *bytesArray, int arrayLength) {
    n = 200;
    *n2 = 3.14f;
    printf("[");
    for (int i = 0; i < arrayLength; ++i) {
        if (i != 0) {
            printf(",");
        }
        printf("%c", bytesArray[i]);
    }
    printf("]\n");
    bytesArray[0] = 'M';
    printf("testFunc函数修改 n: %d, n2: %.2f, bytesArray[0]: %c\n", n, *n2, bytesArray[0]);
}

void sumFunc(int n, int n2, int *result) {
    *result = n + n2;
}

// int *pileTestFunc() 表示返回的是一个指向 int 类型数据的指针的函数
int *pileTestFunc() {
    // 向堆申请 4 个字节的空间,赋值给tmp指针的值*tmp
    int *tmp = (int *) calloc(1, sizeof(int));
    printf("堆中空间栈的tmp指针的地址: %p\n", tmp);
    printf("堆中空间栈的tmp指针的地址存放的值: %d\n", *tmp);
    *tmp = 999;     // 修改值
    printf("堆中空间栈的tmp指针的地址: %p\n", tmp);
    printf("堆中空间栈的tmp指针的地址存放的值: %d\n", *tmp);
    return tmp;     // 返回tmp指针
}

char *pileTestFunc2() {
    // 在字符串常量区创建一个字符串
    char *tmp = "这货不是乌迪尔!";
    printf("字符串常量区tmp指针的地址: %p\n", tmp);
    return tmp;     // 返回tmp指针
}

void swapFunc(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 使用 typedef 定义一个函数指针类型 pFunc
typedef void (*pFunc)(int *, int *);

// pFunc 是一个函数指针类型的标识符,用来声明参数 f 的类型,表示 f 是一个指向函数的指针
void swapFunc2(pFunc f, int *a, int *b) {
    f(a, b);
}

void updatePointer(int **ptr) {
    int newValue = 42;
    *ptr = &newValue;   // 修改传入的指针,使其指向新的值
}

void modifyString(char **str) {
    *str = "Hello Fox!";
}


void testPointer() {
    /**
     * 指针 与 int
     * 值 - 变量 - 内存地址
     */
    printf("======指针与int======\n");

    int ff = 100;
    printf("ff内存地址: %p\n", &ff);        // 000000000061FDDC

//    // 指针变量的定义,此时变量p有自己的内存地址
//    int *p;
//    // 指针变量的初始化, 取ff的内存地址,此时变量p内存地址的值 对应的是ff的内存地址
//    p = &ff;
    int *p = &ff;
    // 此时的*p 就是ff,内存地址是一样的
    printf("*p内存地址: %p\n", &*p);     // 000000000061FDDC
    // p有自己的内存地址,和*p无关
    printf("p内存地址: %p\n", &p);     // 000000000061FDD0
    // p是一个指向整数的指针变量,需要使用%p打印p的值。
    printf("p的值是ff的内存地址: %p\n", p);      // 000000000061FDDC
    *p = 200;
    printf("*p %d\n", *p);      // 200
    printf("ff %d\n", ff);      // 200



    /**
     * 关于const指针常量 三种情况
     *      const int *c  或  int const *c
     *      无法修改值,但是指针的指向可以修改
     *
     *      int *const c
     *      可以修改值,但是指针的指向无法修改
     *
     *      const int *const c
     *      指针指向的变量和变量的值均不能修改
     */

    int cc = 100;
    int ccc = 200;
    printf("cc地址: %p\n", &cc);
    printf("ccc地址: %p\n", &ccc);
//    const int *c = &cc;
//    int *const c = &cc;
    const int *const c = &cc;
    printf("c指针指向的地址: %p\n", c);
//    c = &ccc;
//    *c = 200;
    printf("cc值: %d\n", cc);
    printf("c指针指向的地址: %p\n", c);



    /**
     *  指针 与 字符串

     *  在 C 语言中,str 和 *str 是不同的概念:

        str 是一个指针:
        char *str = "Hello"; 定义了一个指针 str,它指向一个字符串常量 "Hello" 的第一个字符。
        str 是一个指向字符的指针,其值是字符串 "Hello" 在内存中的起始地址。

        *str 是指针指向的内容:
        *str 是对指针 str 的解引用操作,它表示 str 指向的内存地址处存储的值。
        对于字符串 "Hello",*str 表示字符串的第一个字符,即 'H'。
     */

    printf("======指针与字符串======\n");

    char *str = "Hello";
    // 打印指针地址的两种形式
    printf("str指针地址: %p\n", (void *) str);  // 000000000040569F
    printf("str指针地址: %p\n", str);   // 000000000040569F
    printf("str指针指向的内容: %s\n", str);   // Hello
    printf("strChar *str指针内容的第一个字符: %c\n", *str);  // H


    // *str是一个指向字符串常量的指针,这个字符串常量通常存储在只读内存区域中,不能修改。
//    *str = "world!"

    str = "world!";     // 指针可以被重新赋值以指向不同的地址,并没有修改任何字符串常量的内容。
    printf("str指针地址: %p\n", str);
    printf("strUpdate指针指向的内容: %s\n", str);
    printf("strChar *str指针内容的第一个字符: %c\n", *str);



    // 当你使用字符串常量来初始化指针时,指针实际上指向的是常量区中的字符串,
    // 这意味着相同的字符串常量在内存中只会有一份拷贝。
    char *str11 = "str";
    char *str22 = "str";
    printf("str11: %p\n", str11);   // 0000000000405734
    printf("str22: %p\n", str22);   // 0000000000405734
    // 字符数组是存储在栈上的
    // 这样的形式创建字符串时,每个数组都在栈上分配了独立的内存空间。
    char str33[] = "STR";
    char str44[] = "STR";
    printf("str33: %p\n", str33);   // 000000000061FDA4
    printf("str44: %p\n", str44);   // 000000000061FDA0



    /**
     * 指针 与 数组
     *  在 C 语言中,指针的加法操作实际上是按照指针类型所指向的数据类型的大小进行计算的
     *  具体来说,对指针 ptr 执行 ptr + n 操作时,n 会乘以指针所指向的类型的大小。
     *
     *  假设 ptr 是一个指向整型数据的指针,执行 ptr + 1 会使 ptr 指向下一个整型数据的位置。
     *  整型数据在大多数系统中通常是 4 个字节,因此 ptr + 1 实际上是将指针向后移动 4 个字节。
     */
    printf("======指针与数组======\n");

    // 使用指针对int数组元素进行访问
    int array1[] = {100, 200, 300, 400, 500, 600, 700, 800, 900,};
    // 它将一个指向数组第一个元素的指针 aInt 初始化为数组 array1 的第一个元素的地址,
//    int *aInt = &array1[0];
    // 在 C 语言中,数组名会被隐式转换为指向数组第一个元素的指针
    int *aInt = array1;
    // 然后使用指针来访问数组中的元素。
    printf("*aInt --> array1[0]: %d\n", *aInt);
    printf("*aInt --> array1[1]: %d\n", *(aInt + 1));
    printf("*aInt --> array1[2]: %d\n", *(aInt + 2));

    // Char数组访问
    char array2[] = "Hello";
    char *aChar = array2;
    printf("*aChar --> array2[0]: %c\n", *aChar);
    printf("*aChar --> array2[1]: %c\n", *(aChar + 1));
    printf("*aChar --> array2[2]: %c\n", *(aChar + 2));

    // 字符串数组访问
    char *array3[] = {"阿里", "淘宝", "蚂蚁金融", "云弟"};
    // 将一个指向指针的指针(双重指针)aStr 指向 array3 数组的第一个元素
    char **aStr = array3;
    // aStr 是一个指向指针的指针,所以 *aStr 得到的是指针 aStr 所指向的值
    printf("*aStr --> array3[0]: %s\n", *aStr);
    printf("*aStr --> array3[1]: %s\n", *(aStr + 1));
    printf("*aStr --> array3[2]: %s\n", *(aStr + 2));

    // for 遍历
    int length = sizeof array3 / sizeof array3[0];
    int xx = 0;
    for (int i = 0; i < length; ++i) {
        // *aStr++  简单来说: ++ 就是,此次+1,下次循环时用,因为他是后执行
        printf("for -> *aStr --> array3[%d]: %s\n", i, *aStr++);
    }



    /**
     * 函数参数的值传递与指针传递
     */

    printf("======函数参数的值传递与指针传递======\n");
    int t1 = 9;
    float t2 = 1.88f;
    char t3[] = {'a', 'b', 'c', 'd', 'e'};

    // t1 传入的是参数的值
    // t2 传入的是地址
    // t3 传入数组 t3 的首地址(即 t3 的第一个元素的地址)。
    // 在 C 语言中,你无法通过指向数组的指针直接计算数组的长度。
    // 这是因为数组名在函数参数中退化为指针,并且没有任何关于数组大小的信息保留在指针中。
    // 如果函数中需要遍历,则需要额外传入数组的长度
    int t3Length = sizeof t3 / sizeof t3[0];
    testFunc(t1, &t2, t3, t3Length);

    // 在C语言中,函数参数是按值传递的。
    // 这意味着在调用函数时,会将实际参数的值复制到函数的形参中。
    // 因此,在函数内部对形参的修改不会影响到实际参数的值。
    printf("函数参数的值传递: t1: %d, \n", t1);

    // 在函数内部,使用指针可以访问和修改原数据内容。
    printf("函数参数指针传递: t2: %.2f, t3: %c\n", t2, t3[0]);


    // 通常情况下,使用这种写法,只传入k,j变量的值作为参数,将计算的值 通过指针指向的地址来修改res的值
    int res = 0;
    int k = 10;
    int j = 20;
    sumFunc(k, j, &res);
    printf("res: %d\n", res);     // 30



    /**
     * 指针作为函数的返回值
     *   函数内部变量在函数执行完毕会被回收,
     *   所以使用指针作为返回值, 指针要么指向堆区,要么指向常量区,就是不能指向栈区
     */
    printf("=====int类型, 函数返回指针指向堆区, 指针作为函数的返回值======\n");

    // 在C语言中,声明一个指针变量并初始化为 NULL,并不会为这个指针变量分配任何内存空间,
    // 它仅仅是一个空指针,即指向地址为0的内存位置。
    int *d = NULL;      // 定义一个空指针变量 d  这时他没有内存地址
    printf("空指针 d 没有内存地址: %p\n", d);   // 内存地址 0000000000000000
//    printf("空指针 d 没有内存地址,自然也没有值: %d\n", *d);

    d = pileTestFunc();     // 将 d 指针的内存地址赋值给 pileTestFunc 方法中 从堆中新申请的空间地址 tmp 指针地址。

    int e = 200;        // 定义e的作用是刷新栈
    printf("e 无关紧要的输出: %d\n", e);
    printf("当前 d 指针的内存地址就是 tmp 指针的内存地址: %p\n", d);
    printf("当前 d 指针的内存地址的值就是 tmp 指针的内存地址的值: %d\n", *d);
    free(d);


    printf("=====char类型, 函数返回指针指向常量区, 指针作为函数的返回值======\n");

    char *r = NULL;
    printf("空指针 r 没有内存地址: %p\n", r);
    r = pileTestFunc2();
    char h = 'h';
    printf("h 无关紧要的输出: %c\n", h);
    printf("当前 r 指针的内存地址就是 tmp 指针的内存地址: %p\n", r);
    printf("当前 r 指针的内存地址的值就是 tmp 指针的内存地址的值: %s\n", r);

    // 取一个字节就是乱码,因为中文在utf8占 3 个字节
    printf("取指向字符串常量指针 r 指向的第一个字节: %c\n", r);  // �
    /**
     * 当前 r 是一个指向字符串常量的指针。这个字符串在内存中以 UTF-8 编码存储,每个中文字符占用 3 个字节。
     * 字符串 "这货不是乌迪尔!" 在内存中的实际字节表示如下(假设 UTF-8 编码):

        这: 0xE8 0xBF 0x99
        货: 0xE8 0xB4 0xA7
        不: 0xE4 0xB8 0x8D
        是: 0xE6 0x98 0xAF
        乌: 0xE4 0xB9 0x8C
        迪: 0xE8 0xBF 0xAA
        尔: 0xE5 0xB0 0x94
        !: 0xEF 0xBC 0x81
     */
    // 这里指定输出12个字节,刚好是四个UTF-8中文字符的长度
    printf("取指向字符串常量指针 r 指向的前四个字符: %.*s\n", 12, r);



    /**
     * 函数指针
     */
    printf("======函数指针=====\n");

    int qq = 100;
    int ww = 500;
    swapFunc(&qq, &ww);
    printf("使用函数: 交换 qq: %d, ww: %d\n", qq, ww);

    // 初始化一个函数空指针
    void (*pFunc)(int *, int *) =NULL;
    pFunc = swapFunc;
    pFunc(&qq, &ww);
    printf("使用函数指针: 交换 qq: %d, ww: %d\n", qq, ww);


    typedef void(*ppFunc)(int *, int *);
    ppFunc pFunc2 = swapFunc;
    pFunc2(&qq, &ww);
    printf("typedef 定义一个函数指针类型: 交换 qq: %d, ww: %d\n", qq, ww);


    swapFunc2(swapFunc, &qq, &ww);
    printf("typedef 使用函数作为参数: 交换 qq: %d, ww: %d\n", qq, ww);


    /**
     * 多级指针
     *   形参要比实参多一级指针,才能把参数当返回值用
     *   把参数当返回值,传入实参是char * 等指针时,形参就得是二级指针
     */

    printf("======多级指针基础示例=====\n");
    int s = 100;
    int *b = &s;
    int **bb = &b;
    int ***bbb = &bb;
//    *b = 333;
//    printf("int s: %d\n", s);
//    **bb = 555;
//    printf("int s: %d\n", s);
    ***bbb = 777;
    printf("int s: %d\n", s);


    printf("======多级指针 int * 示例=====\n");
    int value = 10;
    int *ptr = &value;
    printf("原始值: %d\n", *ptr);
    updatePointer(&ptr);    // 传入指针的地址,以便在函数中修改指针的值
    printf("更新后的值: %d\n", *ptr);


    printf("======多级指针 char * 示例=====\n");
    char *str9 = "Goodbye, World!";
    printf("原始字符串: %s\n", str9);
    modifyString(&str9);
    printf("修改后的字符串: %s\n", str9);

}

// 向堆区申请内存
char *hexFunc(char *array, int length) {
    // 在将每个字节转换为两个十六进制字符时,每个字节需要两个字符来表示, 外加一个结尾的空字符 '\0'
    char *toHexSpace = (char *) malloc(length * 2 + 1);
    if (toHexSpace == NULL) {
        fprintf(stderr, "Memory allocation failed.\n");
        exit(1);
    }
    // 将数组中的每个字节转换为十六进制,并存储在缓冲区中
    for (int i = 0; i < length; ++i) {
        // hexArray + i * 2是一个指针运算,
        // 它的意思是将hexArray指针向后移动i * 2个字节的位置。
        // 这是因为在转换成十六进制时,每个字节需要两个十六进制字符来表示,所以在结果中需要跳过相应的位置。
        // sprintf函数会将每个array中的字节转换为两个十六进制字符,并将结果存储到hexArray中,
        // 每次从hexArray的当前位置开始存储,直到存储完所有字节的十六进制表示。
        sprintf(toHexSpace + i * 2, "%02x", array[i]);
    }
    toHexSpace[length * 2] = '\0';  // 添加字符串结尾的空字符

    return toHexSpace;
}

// 使用指针变量
char *hexFunc2(char *array, int length, char *toHexSpace) {
    // 将数组中的每个字节转换为十六进制,并存储在缓冲区中
    for (int i = 0; i < length; ++i) {
        sprintf(toHexSpace + i * 2, "%02x", array[i]);
    }
    toHexSpace[length * 2] = '\0';  // 添加字符串结尾的空字符
}

/**
 * 关于栈:

 *  1,在内存中,栈是从高地址向低地址增长的

 *  2,空间占用: (int为例)
      一个 int 类型通常在大多数系统中占据 4 个字节的内存空间。
      因此,如果一个 int 变量的内存地址是某个特定值(比如一个地址为 0x1000 的整型变量),
      那么这个 int 变量在栈中将占据从 0x1000 到 0x1003 的四个字节的空间。

 *  3,每个字节以二进制的形式存储: (int 100为例)
      地址 0x1000 的字节的值是 00000000(十进制 0)
      地址 0x1001 的字节的值是 00000000(十进制 0)
      地址 0x1002 的字节的值是 00000000(十进制 0)
      地址 0x1003 的字节的值是 01100100(十进制 100)


 * 向堆中申请内存空间:
 *
 * 内存操作相关函数
 *   malloc、calloc、realloc、memset、memcpy、free
 *   需要引入stdlib.h string.h 头文件
 *
 * void* 代表任意类型,强制类型转换
 *   char *realResult = (char *)malloc(33);
 *
 * 向堆区申请内存空间基本步骤
 *   申请、置0、使用、释放
 */

void pileFunc() {
    /**
     * malloc(Memory Allocation)是 C 标准库中的一个函数,用于在堆上动态分配指定大小的内存块。

    动态内存分配:
        malloc 允许程序在运行时请求特定大小的内存块,而不是在编译时确定内存需求。
        这对于需要在程序执行过程中灵活管理内存的情况非常有用。

    返回指针:
        malloc 返回一个指向所分配内存块的指针。如果内存分配失败,它返回 NULL。
        返回的指针类型是 void*,可以转换为任何所需的指针类型。

    手动内存管理:
        使用 malloc 分配的内存不会在函数返回后自动释放。
        程序员需要使用 free 函数手动释放这块内存,以防止内存泄漏。

    内存初始化:
        malloc 只分配内存,但不对内存进行初始化。分配的内存中的数据是未定义的。
        未定义的数据中会存在垃圾数据,如果需要清理内存,需要使用 memset
     */
    printf("malloc 分配内存空间=====\n");
    // malloc(10 * sizeof(int)) 向堆区申请内存,返回的类型是 void*,即指向 void 的指针。
    // 因为 malloc 可以分配任意类型的内存空间,
    // 所以它返回的是一个通用指针类型,需要根据实际情况进行类型转换。
    // 这里使用 (int *) 强转成 int
    int *array = (int *) malloc(10 * sizeof(int));

    // 申请出来的内存可能会有垃圾值
    // 使用 memset(数组变量, 设置值, 数组大小)
    memset(array, 0, 40);

    *array = 100;           // 将第一个整数设为 100
    *(array + 1) = 200;     // 将第二个整数设为 200
    array[2] = 300;         // 将第三个整数设为 300
    for (int i = 0; i < 10; ++i) {
        printf("array[%d]: %d\n", i, array[i]);
    }
    // 使用 malloc() 申请空间, 需要使用 free 释放内存
    // 否则该空间在程序结束时才会销毁
    free(array);            // 释放内存空间


    printf("内存地址分配从低到高=====\n");
    int *arr1 = (int *) malloc(16);
    int *arr2 = (int *) malloc(16);
    int *arr3 = (int *) malloc(32);
    int *arr4 = (int *) malloc(64);
    printf("%p\n", arr1);    // 0000000000B52460
    printf("%p\n", arr2);    // 0000000000B52480
    printf("%p\n", arr3);    // 0000000000B524A0
    printf("%p\n", arr4);    // 0000000000B524D0


    /**
     * calloc 是 C 标准库中的一个函数,用于动态分配内存并将分配的内存初始化为零。
     * 它的主要作用是分配一块可以容纳指定数量元素的连续内存块,并将这些内存块的内容初始化为零。
     * 与 malloc 不同的是,calloc 在分配内存的同时会将内存块清零。
     */

    printf("calloc 分配内存空间: 5个int,也就是20个字节=====\n");

    int *array2 = (int *) calloc(5, sizeof(int));

    // 在 array2 创建后,再创建array3,则realloc无法在原地扩容
//    int *array3 = (int *) calloc(10, sizeof(int));

    *array2 = 100;
    *(array2 + 1) = 200;
    array2[2] = 300;

    for (int i = 0; i < 5; ++i) {
        printf("array2[%d]: %d\n", i, array2[i]);
    }

    printf("初始化array2内存地址: %p\n", array2);     // 0000000000C92520

    /**
     * realloc
     *    1,如果可以在原地扩展,则在 array2 基础上进行扩容,原 array2 的地址和扩容后地址一致。
     *
     *    2,如果需要扩容的内存块无法在原地扩展,则会分配一个新的内存块,将原有数据复制到新的内存块中,然后释放原有 array2 内存块。
     */

    printf("realloc 扩容=====\n");

    array2 = realloc(array2, 10 * sizeof(int));

    for (int i = 0; i < 15; ++i) {
        printf("array2[%d]: %d\n", i, array2[i]);
    }

    // 如无法在原地址扩容,则使用新地址,释放原地址
    printf("扩容后array2内存地址: %p\n", array2);     // 0000000000C92520

    free(array2);

}

struct hobby2 {
    char *h1;
    char *h2;
    char *h3;
};
struct Person2 {
    char *name;
    int age2;
    char sex;
    struct hobby2 hobbies;
};

void testStruct(struct Person2 p) {
    p.name = "梁朝伟";
}

void testStruct2(struct Person2 *p) {
    p->name = "梁朝伟";  // 使用指针语法访问结构体成员
}

struct Person2 test() {
    struct Person2 p = {"周杰伦", 30, 'g', {"唱歌", "跳舞", "弹钢琴"}};
    return p;
}

struct Person2 *test2() {
    struct Person2 *p = (struct Person2 *) calloc(1, sizeof(struct Person2));
    *p = (struct Person2) {"周杰伦", 30, 'g', {"唱歌", "跳舞", "弹钢琴"}};
    return p;
}


void structFunc() {
    /**
     * 结构体初始化
     */
    // 定义结构体
    struct People {
        char *name;
        int age;
        char sex;
    } p1, p2;       // 初始化1

    p1.name = "John";
    p1.age = 18;
    printf("p1:  %d\n", p1.age);

    // 初始化2
    struct People p3, p4;
    p3 = (struct People) {"King", .sex='m'};
    printf("p3: %s\n", p3.name);

    // 初始化3
    struct People p5 = {"David", 56, 'g'};
    struct People p6 = {"Jack",};
    struct People p7 = {.age=88, .name="Robin",};
    printf("p7: %s,%d\n", p7.name, p7.age);


    /**
     * 结构体的赋值
     */
    printf("p6: %s\n", p6.name);
    p6 = p7;
    printf("p6: %s,%d\n", p6.name, p6.age);
    p6.name = p5.name;
    printf("p6: %s,%d\n", p6.name, p6.age);


    /**
     * 结构体数组
     */
    struct People pArr[10] = {
            {"John", 18, 'm'},
            {"Mary", 20, 'f'},
            {"Tom",  25, 'g'},
    };
    int len = sizeof(pArr) / sizeof(struct People);
    printf("pArr: %d\n", len);  //  10;
    for (int i = 0; i < len; ++i) {
        printf("pArr[%d]: %s,%d,%c\n", i, pArr[i].name, pArr[i].age, pArr[i].sex);

    }


    /**
     * 结构体指针
     */
    struct People p8 = {"David", 56, 'g'};
    struct People *pp = &p8;
    printf("*pp: %s\n", (*pp).name);    // 写法1,
    printf("*pp: %s,%d,%c\n", pp->name, pp->age, pp->sex);  // 写法2,



    /**
     * 结构体嵌套
     */

    struct hobby {
        char *h1;
        char *h2;
        char *h3;
    };
    struct Person {
        char *name;
        int age;
        char sex;
        struct hobby hobbies;
    };
    struct hobby h = {"抽烟", "喝酒", "烫头"};
    struct Person p9 = {"于大爷", 65, 'g', h};
    printf("person p9 name=%s age=%d sex=%c hobby1=%s hobby2=%s hobby3=%s\n", p9.name, p9.age, p9.sex,
           p9.hobbies.h1, p9.hobbies.h2, p9.hobbies.h3);


    struct Person p10 = {"郭子", 19, 'g', {"唱歌", "跳舞", "反三俗"}};
    printf("person p10 name=%s age=%d sex=%c hobby1=%s hobby2=%s hobby3=%s\n", p10.name, p10.age, p10.sex,
           p10.hobbies.h1, p10.hobbies.h2, p10.hobbies.h3);



    /**
     * 结构体作为函数参数
     * 结构体指针作为函数参数
     *  testStruct函数形参无法识别 本函数内定义的结构体,所以在全局定义了 Person2
     */
    printf("=====结构体作为函数参数=====\n");
    // 在C语言中,当你将结构体传递给函数时,传递的是结构体的副本。(而且比较占用栈空间)
    // 因此,函数内对结构体成员的修改不会影响到函数外的原始结构体
    struct Person2 p11 = {"陈冠希", 19, 'g', {"唱歌", "飙车", "搞摄影"}};
    testStruct(p11);
    printf("%s\n", p11.name);

    printf("=====结构体指针作为函数参数=====\n");
    // 需要传递结构体指针来修改原始结构体(只需要一个指针 8 个字节,节省栈空间)
    struct Person2 *p12 = &p11;
    testStruct2(p12);
    printf("%s\n", p11.name);
    printf("%s\n", p12->name);  // 当前p12 本质上就是 p11



    /**
     * 结构体作为函数返回值
     * 结构体指针作为函数返回
     */
    printf("=====结构体作为函数返回值=====\n");
    struct Person2 p13 = test();
    printf("%s\n", p13.name);

    printf("=====结构体指针作为函数返回=====\n");
    struct Person2 *p14 = test2();
    printf("%s\n", p14->name);
    free(p14);



    /**
     * 匿名结构体
     *   只能在声明结构体类型的同时声明结构体变量
     */
    struct {
        char *name;
        int age;
    } p15;
    p15.name = "娄得瓦";
    p15.age = 18;
    printf("匿名结构体: name= %s, age= %d\n", p15.name, p15.age);


    /**
     * typedef
     */
    printf("=====typedef=====\n");
    unsigned long long int yy = 100;
    printf("yy: %llu\n", yy);

    typedef unsigned long long int ut;  // 相当于起别名
    ut zz = 500;
    printf("zz: %llu\n", zz);



}

int main() {
    // printf 没有换行效果
    printf("Q\tWE");
    printf("Hello, World!\n");

    printf("======各数据类型及占位符格式化输出======\n");
    dataType();

    printf("======字符串格式化输出======\n");
    strFormat();

    printf("======类型的批量声明======\n");
    batchStatement();

    printf("======算术运算======\n");
    calculate();

    printf("======sizeofMethods======\n");
    sizeofMethods();

    printf("======数组======\n");
    array();

    printf("======字符串======\n");
    strMethods();

    printf("======宏定义======\n");
    macro();

    printf("======函数声明调用======\n");
    int sum = funcMethod(5, 10);
    printf("函数返回值: %d\n", sum);

    funcMethod2("可口可乐");

// 预处理指令#include 拷贝文件代码,注意:在函数中不能再次创建函数
#include "demo1.txt"


// 预处理指令#if 只能用于#define定义出来的常量是否为真,LEN为真则走#if
#if LEN
    printf("Debugging information\n");
#else
    printf("No debugging information\n");
#endif
    printf("#endif不知道有啥用,也不想知道\n");


    int resultSum = funcMethod99(20, 10);
    printf("自定义头文件,声明函数 funcMethod99(20, 10) 返回乘积结果: %d\n", resultSum);


    printf("引入变量aa值为: %d\n", aa);


    printf("======局部静态变量测试======\n");
    for (int i = 0; i < 5; ++i) {
        testStatic();
    }

    printf("======全局静态变量测试======\n%d\n", abc);

    printf("======静态函数======\n");
    testStaticFunc();

    printf("======指针======\n");
    testPointer();

    printf("======内存操作======\n");
    pileFunc();

    printf("======指针的复习,Hex编码======\n");
    // 1, hexFunc 向堆区申请空间
    char array[] = {95, 95, 26, 101, 127, 29, 30, 31, 32, 33};
    int length = sizeof(array) / sizeof(char);
    char *toHex = hexFunc(array, length);
    printf("toHex: %s\n", toHex);
    free(toHex);   // 释放内存可以使用任意指向该内存的指针变量,并非必须申请时的指针变量

    // 2, hexFunc2 使用指针变量
    char toHexSpace[length * 2 + 1];
    hexFunc2(array, length, toHexSpace);
    printf("toHex2: %s\n", toHexSpace);


    printf("======结构体=====\n");
    structFunc();


    return 0;
}

// 函数定义
int funcMethod(int a, int b) {
    return a + b;
}

HeaderDemo.h  头文件

//
// Created by ASUS on 2024/6/9.
//

#ifndef CPRO_HEADERDEMO_H
#define CPRO_HEADERDEMO_H

// 头文件函数声明
int funcMethod99(int a, int b) {
    return a * b;
}

#endif //CPRO_HEADERDEMO_H

 

demo1.txt  文本文件

float f = 10.5f;
printf("这是demo1.txt文件中的输出示例!float为: %.1f\n", f);

demo2.c  源文件

//
// Created by ASUS on 2024/6/9.
//

int aa = 999;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值