# C语言

# 换行符

  • windows: /r/n
  • mac:/r
  • linux:/n

# printf(参数1,参数2)

  • 参数1(必填):输出样式最终体现形式
  • 参数2(选填):输出格式
    • 整形:%d
    • 实型:%f
    • 字符:%c
    • 字符串:%s
    • 无符号:%u
    • 内存地址:%p
    • 内存大小 %zu
#include <stdio.h>
int main() {
	int sum = 100 + 200;
	int ab = 1E2;
	printf("最终计算结果 %d",sum);
	
	//无符号打印
    unsigned int ser = 123;
    printf("%u", ser);
    
    //实型,保留两位
    float c = 1.123;
    printf("%.2f", c);
    
    //双精度
    double d = 1.123123;
    printf("\n %lf", d);

    
	return 0;
}

# 进制

  • 二进制:2位一个整体:0b开头
ob10
ob11
  • 八进制:三位数一个整体,0-7组成,0开头
010
011
  • 十六进制:四位一个整体,0-9加油a-f(a堪称10,b看成11,以此类推)组成,0x开头
0x10
0x11

# 数据类型

隐式转换规则:

  1. 取值范围小的,和取值范围大的计算,小的会自动提升为大的,再进行运算
  2. short cha r类型的数据在运算的时候,先提升为int,再进行运算

取值范围
double > float > long long > long > int > short > char

触发条件
在进行计算,赋值等操作,会触发

  • 符号表示

    • signed 有符号整数
    • unsigned 无符号整数
  • 整数

    • short 2 字节
    • int 4 字节
    • long 4(window | linux 32位) | 8(linux 64位) 字节
    • longlong(c99) 8 字节
  • 小数

    • float
    • double
  • 字符

    • char

# 数组长度计算

  • 公式:总长度 / 数据类型占用的字节个数
    int arr[] = {1,2,3,4,5,6,2,1,1};
    arr[0] = 100;

    int len = sizeof(arr) / sizeof(arr[0]);
    printf("%d", len);

# 数组常见问题

  • 数组作为参数传递 根据上面的计算数组长度,如果在参数中传递,进行计算会有问题
    数组在定义处表示的是完整地址,但是传为参数传递是的首地址,如果需要遍历则需要把长度传递
    • 错误情况1
int main() {
    int arr[] = {1,2,3,4,5,6,2,1,1};
    printArr(arr);
    return 0;
}

void printArr(int arr[]) {
    int size = sizeof(arr) / sizeof(arr[0]);
    printf("数组的长度 %d", size); //长度为2
}
  • 正确情况
int main() {
    int arr[] = {1,2,3,4,5,6,2,1,1};
    int size = sizeof(arr) / sizeof(arr[0]);
    printf("数组的长度 %d", size);
    printArr(arr, size);
    return 0;
}

void printArr(int arr[], int length) {
    for (int i = 0; i < length; ++i) {
        printf("数组的内容 %d\n", arr[i]);
    }
}

  • 使用数组名进行计算的时候,退化为只想第一个元素的指针,此时不再表示那个整体了

# 二维数组

语法:数据类型 变量名[m][n] =

  • m:二维数组长度
  • n:一维数组长度
#include <stdio.h>

int main() {
    int arr[2][2] = {
        {1, 2},
        {3, 4}
    };

    // 遍历
    int rows = sizeof(arr) / sizeof(arr[0]);
    int cols = sizeof(arr[0]) / sizeof(arr[0][0]);

    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }

    return 0;
}

//遍历方式2
#include <stdio.h>

int main() {
    int arr1[3] = {1, 2, 3};
    int arr2[4] = {1, 2, 3, 4};
    int arr3[5] = {1, 2, 3, 4, 5};

    //在之前先计算数组长度,不然再后面计算的是错误的
    int arrIndex = sizeof(arr1) / sizeof(int);
    int arr2Index = sizeof(arr2) / sizeof(int);
    int arr3Index = sizeof(arr3) / sizeof(int);
    //将长度放在数组中
    int lengthArr[3] = {arrIndex, arr2Index, arr3Index};

    int* arr[3] = {
        arr1,
        arr2,
        arr3
    };

    //遍历
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < lengthArr[i]; j++) {
            printf("%d  ", arr[i][j]);
        }
        printf("\n");
    }

    return 0;
}

//通过指针遍历
#include <stdio.h>

int main()
{
    int arr[2][5] =
    {
        {1, 2},
        {1, 2, 3, 4, 5}
    };

    //二位数组的指针索引
    int (*p) [5] = arr;
    for (int i = 0; i < 2; i++)
    {
        //遍历一维数组
        for (int j = 0; j < 5; j++)
        {
            printf("%d ", *(*p + j));
        }
        printf("\n");
        //二维数组指针+1(+1的意思就是一个字节,一个字节是4个bit)
        //二维数组里面的一维数组是5大小,内容是int,一个int是4个字节
        //4 * 5 一共就占用了20个字节,当指针+1的时候就是 20个字节 + 4,就到下一个数组了
        p++;
    }
}

#include <stdio.h>

int main()
{
    int a[2] = {1, 2};
    int b[2] = {3, 4};


    int* arr[2] =
    {
        a,
        b
    };


    //二位数组的指针索引
    int **p = arr;
    for (int i = 0; i < 2; i++)
    {
        //遍历一维数组
        for (int j = 0; j < 2; j++)
        {
            printf("%d ", *(*p + j));
        }
        printf("\n");
        //二维数组指针+1(+1的意思就是一个字节,一个字节是4个bit)
        //二维数组里面的一维数组是5大小,内容是int,一个int是4个字节
        //4 * 5 一共就占用了20个字节,当指针+1的时候就是 20个字节 + 4,就到下一个数组了
        p++;
    }
}

# 命名规范

  • 变量名:全部小写
  • 代码文件名:全部小写,单词用下划线分开

# 跳转 goto

#include <stdio.h>

int main()
{
    printf("第一行");
    goto five;
    printf("第二行");
    printf("第三行");
    printf("第四行");
    five:char str[20] = "第五行";
    printf("%s", str);

    return 0;
}

# 函数的注意事项

函数的申明往往需要在main函数的上方,不然调用的话会报错,找不到函数,如果一定要在别的下方申明需要在顶部申明

#include <stdio.h>
int getMonth();
void printYear();

int main()
{
    printf("%d", getMonth());
    return 0;
}

int getMonth() {
    printYear();
    return 2;
}

void printYear() {
    printf("7月份");
}

# 常见函数库

使用别的库需要导入

#include <stdio.h>
#include <time.h>

int main()
{
    long long res = time(NULL);
    printf("%d", res);
    return 0;
}

获取随机数

#include <stdio.h>
#include <stdlib.h>


int main()
{
    //种子
    srand(2);
    for (int i = 0; i < 10; ++i) {
        //获取随机数 
        int number = rand();
        printf("%d\n", number);
    }

    return 0;
}

获取随机数在某个范围内 公式:rand() % ((尾数 + 1) - 开头) + 开头

  • 7 - 23

# 指针

  • 语法:属性类型* 指针名称 = &变量名称
  • 内存占用:与类型无关,跟编译器有关:32位 4字节 64位 8字节
int main() {
    int a = 11;
    int* p = &a;
    printf("%d\n", *p);
    *p = 22;
    printf("%d", *p);
    return 0;
}

细节

函数中的变量的生命周期跟函数相关,函数结束了,变量也回收了此时其他函数中,就无法使用了。 如果希望不被回收,可以增加static,增加了之后这个变量,会随着程序一直结束

#include <stdio.h>
int* getStaticNumber();

int main() {
    int *lastingNumber = getStaticNumber();
    printf("获取到的number %d", *lastingNumber);
    return 0;
}

int* getStaticNumber() {
    static int a = 10;
    return &a;
}

  • 二级指针语法:指针类型** 指针名
#include <stdio.h>

int main() {
    int a = 30;
    int b = 20;
    
    int *p = &a;
    int** pp = &p;
    
    *pp = &b;
    printf("%d", **pp);
    return 0;
}

# 指针的作用

  1. 操作其他函数的变量
    意思就是在值传递的时候,别的修改只是当前作用域的没有修改原数据内容
 #include <stdio.h>
void swap(int *a, int *b);

int main() {

    int a = 10;
    int b = 20;
    printf("修改前的内容 %d ,%d \n", a, b);
    swap(&a, &b);
    printf("修改后的内容 %d ,%d", a, b);
    return 0;
}

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}
  1. 函数返回多个值(伪返回)
#include <stdio.h>
void getMaxAndMin(int arr[], int len, int *max, int *min);

int main() {
    int arr[5] = {1, 1231, 321, 0};
    int len = sizeof(arr) / sizeof(arr[0]);
    int max = arr[0];
    int min = arr[0];

    getMaxAndMin(arr, len, &max, &min);
    printf("最大值 %d, 最小值 %d", max ,min);

    return 0;
}


void getMaxAndMin(int arr[], int len, int *max, int *min) {
    for (int i = 0; i < len; ++i) {
        if(arr[i] > *max) {
            *max = arr[i];
        }

        if(arr[i] < *min) {
            *min = arr[i];
        }
    }
}

# 函数指针

#include <stdio.h>

void method1();
int method2(int num1, int num2);

int main()
{
    //无参就是空的
    int (*p)() = method1;
    //类型根据函数申明的来
    int (*p2)(int, int) = method2;


    p();
    int sum = p2(1, 2);
    printf("计算的结果 %d ", sum);

}

void method1() {
    printf("method \n");
}

int method2(int num1, int num2) {
    printf("%d   %d \n", num1, num2);
    return num1 + num2;
}

# 案例

#include <stdio.h>

int add(int num1, int num2);
int subject(int num1, int num2);
int mult(int num1, int num2);
int deiv(int num1, int num2);


int main()
{
    int (*p[4])(int, int) = {
        add,
        subject,
        mult,
        deiv
    };

    printf("请输入两个数\n");
    fflush(stdout);

    int num1, num2;

    scanf("%d%d", &num1, &num2);
    printf("第一个数  %d\n", num1);
    printf("第二个数  %d\n", num2);


    printf("请选择一个数字来进行计算\n 1 加法 \n 2 减法 \n 3 乘法 \n 4 除法\n");
    fflush(stdout);
    int chose;
    scanf("%d", &chose);

    int sum = p[chose - 1](num1, num2);
    printf("计算的结果 %d", sum);


    return 0;
}


int add(int num1, int num2) {
    return num1 + num2;
}

int subject(int num1, int num2) {
    return num1 / num2;
}

int mult(int num1, int num2) {
    return num1 * num2;
}

int deiv(int num1, int num2) {
    return num1 / num2;
}

# 字符串

  • 语法1:char 变量[字符长度] = “内容”;
  • windows中一个中文占用2个字节
char str[4] = "str"; 
  • 注意点
    • 在底层时还是以字符数组存储的
    • 数组长度要么不写,要么预留一个结束标记 (结束标记是 \0 ) 的空间,不然会出现错误的字符
      • 如:char str [3] = "str"; 这里就会出现问题,应该多加一位
int main() {
    //错误的
    char str[3] = "str";
    printf("%s\n", str);
    //正确的
    char str2[4] = "str";
    printf("%s\n", str2);

    return 0;
}
  • 语法2:char* 变量名 = ""
    注意点,这种定义的字符,底层才会放到只读的常量区
int main() {
    char *str = "str1";
    printf("%s", str);
    return 0;
}


# 常见函数

需要先导入头文件,#include <string.h>

  • strlen:获取字符串长度
  • strlen:获取字符串的长度
  • strcat:拼接两个字符串
  • strcpy:复制字符串
  • strcmp:比较两个字符串
  • strlwr:将字符串变成小写
  • strupr:将字符串变成大写

# 结构体

语法:struct 结构体名称 { 变量类型 变量名称; }

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

struct Student {
    int age;
    char name[100];
};

int main() {
    struct Student student;
    student.age = 18;
    strcpy(student.name, "张三");

    printf("年龄 %d", student.age);
    printf("姓名 %s", student.name);
    return 0;
}

# 结构体嵌套

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

typedef struct {
    char address[100];
} Address;

typedef struct  {
    int age;
    char name[100];
    Address address;
} S;



int main() {
    S student;
    strcpy(student.name, "张三");
    student.age = 18;
    strcpy(student.address.address, "上海市");
    printf("姓名 %s \n", student.name);
    printf("年龄 %d\n", student.age);
    printf("地址 %s\n", student.address.address);

    return 0;
}

# 结构体作为函数的参数传递

如果想在函数中修改结构体需要以内存地址传递

#include <stdio.h>

//struct 可省略
typedef struct {
    int age;
    int sex;
} S;

//声明的方法,如果用到结构体,需要在结构体下方
//不然编译的时候找不到
void updateAge(S* s);

int main() {
    S student = {
        1,
        1
    };
    printf("初始年龄 %d", student.age);
    updateAge(&student);
    printf("修改后的年龄 %d", student.age);
    return 0;
}

void updateAge(S* s) {
    s->age = 30;
}

# 别名

语法:typedef struct { 变量类型 变量名称; } 别名名称;

//struct 可省略
typedef struct {
    int age;
    int sex;
} s;


int main() {
    s student = {
        1,
        1
    };
    printf("年龄 %d",student.age);
    return 0;
}clipo

# 内存对齐

  • 规则:内存地址 / 占用字节 = 结果可以整除
  • 结构体的内存对齐
    • 结构体的总大小,是最大类型的整数倍

# 共同体

语法:

 union 变量名称 {
  数据类型 变量名称;
}

特点:

  • 共用体,也叫联合体,共同体
  • 所有的变量都使用同一个内存空间
  • 所占的内存大小=最大成员的长度(也受内存对齐影响)
  • 每次只能给一个变量进行赋值,因为第二次赋值时会覆盖原有的数据

用例:

#include <stdio.h>
#include <string.h>
union Money {
  int moneyoi;
  char moneyStr[10];
  double moneyd;
};

int main() {

  union Money money;
  
  // money.moneyoi = 100;
  // money.moneyd = 1.11;
  strcpy(money.moneyStr, "100元");
  
  printf("%s", money.moneyStr);
  return 0;
}

# 动态内存

  • malloc:申请连续控件

  • calloc:申请空间+数据初始化(用的少)

  • realloc:修改空间大小

  • free:释放空间

  • 细节

    • malloc创建空间的单位是字节
    • malloc返回的是void类型的指针,没有步长的概念,也无法获取空间中的数据,需要强转
    • malloc返回的仅仅是首地址,没有总大小,最好定义一个变量记录总大小
    • malloc申请的空间不会自动消失,如果不能正确释放,会导致内存泄露
    • malloc申请的空间过多,会产生虚拟内存
    • malloc申请的空间没有初始化值,需要先赋值才能使用
    • free释放完空间之后,空间中数据叫做脏数据,可能被清空,可能被修改为其他值
    • calloc就是在malloc的基础上多一个初始化的动作
    • realloc修改之后的空间,地址值有可能发生变化,也有可能不会改变,但是原本的数据不会丢失
    • realloc修改之后,无需释放原来的空间,函数底层会进行处理

# malloc

#include <stdio.h>
#include <stdlib.h>

int main() {
    //单位是字节,填写100的话,就是申请100个连续的字节地址
    int *p = malloc(100 * sizeof(int));

    printf("%p\n", p);

    for (int i = 0; i < 100; i++) {
        // *(p + i) = (i + 1) * 10;
        //第二种写法
        //p[i] 会解析成 p + 1
        p[i] = (i + 1) * 10;

    }

    for (int i = 0; i < 100; i++) {
        printf("%d\n", *(p + i));
    }

    return 0;
}

# calloc

#include <stdio.h>
#include <stdlib.h>

int main() {
    //单位是字节,填写100的话,就是申请100个连续的字节地址
    int *p = calloc(100, sizeof(int));

    for (int i = 0; i < 100; ++i) {
        printf("%d\n", *(p + 1));
        
    }
    return 0;
}

# realloc

#include <stdio.h>
#include <stdlib.h>

int main() {
    //单位是字节,填写100的话,就是申请100个连续的字节地址
    int *p = malloc(100 * sizeof(int));


    for (int i = 0; i < 100; ++i) {
        printf("%d\n", *(p + 1));
    }

    //修改之后不会改变原有数据,会拷贝到一个新的
    realloc(p, 20 * sizeof(int));
    return 0;
}

# free

#include <stdio.h>
#include <stdlib.h>

int main() {
    //单位是字节,填写100的话,就是申请100个连续的字节地址
    int *p = malloc(100 * sizeof(int));


    for (int i = 0; i < 100; ++i) {
        printf("%d\n", *(p + 1));
    }

    //修改之后不会改变原有数据,会拷贝到一个新的
    realloc(p, 20 * sizeof(int));
    free(p);
    return 0;
}

# IO

  • 打开文件:fopen
  • 读取:
    • fgetc:一次读一个字符
    • fgets:一次读一行
    • fread:一次读多个
    • fputc
    • fputs
    • fwrite
  • 关闭:fclose

# 操作模式

  • r:只读
  • w:只写
    • 文件不存在,创建新文件
    • 文件已存在,清空文件
  • a:追加写
    • 文件不存在,创建新文件
    • 文件已存在,不清空文件,续写
  • rb:只读模式(操作二进制文件)
  • wb:只写
    • 文件不存在,创建新文件
    • 文件已存在,清空文件
  • ab:追加写(操作二进制文件)
    • 文件不存在,创建新文件
    • 文件已存在,不清空文件,续写

# 案例:

int main() {
    FILE* file = fopen("D:/clion/workspace/helloworld/news/a.txt", "r");
    char arr[1024];
    char* str;
    while ((str = fgets(arr, 1024, file)) != NULL) {
        printf("%s", arr);
        
    }
    fclose(file);
    return 0;
}  
  1. 指定一次读多少
#include <stdio.h>

int main() {
    FILE* file = fopen("D:/clion/workspace/helloworld/news/a.txt", "r");
    char arr[4];
    int n;
    /**
     * 传入一个数组,存放读取到的数据
     * 数组每个数据占用多少字节
     * 数组长度
     * file
     */
    while ((n = fread(arr,1,4,file)) != 0) {
        for (int i = 0; i < n; ++i) {
            printf("%c", arr[i]);
        }
    }
    fclose(file);
    return 0;
}
  1. 写出
#include <stdio.h>

int main() {
    FILE* file = fopen("D:/clion/workspace/helloworld/news/b.txt", "w");
    int c = fputc(97, file);
    printf("%c", c);

    //写出成功返回一个非负数
    fputs("你好", file);

    /**
     * 写出的内容
     * 元素占用的字节
     * 写出数据总长度
     * file
     */
    char arr[] = {101, 102};
    fwrite(arr, 1, 2, file);
    
    fclose(file);
    return 0;
} 

# 枚举 enum

#include <stdio.h>

enum Status {
    LOWER = 0 ,
    MIDDLE =  1,
    HIGH = 2,
};

int main(void) {
    enum Status lower = MIDDLE;
    printf("%d \n", lower);

    switch (lower) {
        case LOWER : {
            printf("低档位");
            break;;
        }
        case MIDDLE : {
            printf("中档位");
            break;
        }
        case HIGH: {
            printf("高档位");
            break;
        }
    }
    return 0;
}