• C开发环境与基础


    1.基本类型

    1.1整型

    整型有几个子类型,默认有符号signed,无符号可表示的最大值翻倍,int可省,极值在头文件limits.h定义,如ULLONG_MAX:unsigned long long 的最大值
    unsigned short int a;
    unsigned long int b;
    unsigned long long int c= 1234ULL;(类似Java的后缀)
    不同位数系统short long longlong字节宽度可能不同,stdint.h定义了屏蔽系统差异的便移植类型如int32,如果系统int类型为32位,int32_t就会指向int;如果long类型为32位,int32_t则会指向long;其它还有int_least8_t、int_fast8_t、intptr_t(存放指针的整型)等

    1.2. 布尔

    0=false;非0=true,引入stdbool.h才能使用true/false关键字,可以与0/1互换

    1.3.其它

    字符、浮点数类似Java,C也可以进行隐式显示类似转换

    溢出错误案例

     unsigned char xx = 255;
        xx=xx+1;
        printf("%d\n", xx); // 0
        xx=xx-1;
        printf("%d\n", xx); // 255
    
    • 1
    • 2
    • 3
    • 4
    • 5
    for (unsigned int i = n; i >= 0; --i) // 溢出导致死循环
    
    • 1
    unsigned int i = 5;
    unsigned int j = 7;
    
    if (i - j < 0) // 错误
      printf("negative\n");
    else
      printf("positive\n");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    unsigned int ui;
    unsigned int sum;
    
    // 错误
    if (sum + ui > UINT_MAX) too_big();
    else sum = sum + ui;
    
    // 正确
    if (ui > UINT_MAX - sum) too_big();
    else sum = sum + ui;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.指针类型

    • 传入指针,即指向int p的地址,故对该地址的p修改函数外也能获取修改后的值,类似Java引用传递
    void increment(int* p) {
      *p = *p + 1;
    }
      int x=1;
      increment(&x);//传入x的地址
      printf("%d",x);//2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • *运算符为取内存地址的值,&p为取p的内存地址 p == *(&p)
    • 指针运算
    int* p;
    *p = 1; // 错误,指针类型默认执行随机地址,不能读写;初始化后的指针可以加减整数表示指针移动;
    
    • 1
    • 2
    short* j1;
    short* j2;
    j1 = (short*)0x1234;
    j2 = (short*)0x1236;
    ptrdiff_t dist = j2 - j1; //一种带符号的整数类型
    printf("%d\n", dist); // 指针不能相加但可以相减,表示它们的距离,这里距离为1(但是内存地址相差2个bit),2个字节正好存放1个 short 类型的值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.函数可变参数

    需要使用#include "stdarg.h"里面定义的几个函数来处理

    double average(int i, ...) {
        double total = 0;
        va_list ap;//接收参数
        va_start(ap, i);//初始化可变参数,i为前一个参数,用于初始化定位
        for (int j = 1; j <= i; ++j) {
            total += va_arg(ap, double);//每次访问,自动指向下一个地址
        }
        va_end(ap);//清理对象
        return total / i;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4.修饰符

    4.1 static修饰符

    • static修饰内部变量,只初始化一次,但只能函数内可以访问
    #include 
    
    void counter(void) {
      static int count = 1;  // 只初始化一次
      printf("%d\n", count);
      count++;
    }
    
    int main(void) {
      counter();  // 1
      counter();  // 2
      counter();  // 3
      counter();  // 4
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • static修饰常量(默认初始值0)和方法,只能该文件访问,其它文件访问编译报错

    定义原文件test.c

    #include 
    
    int count1 = 1;
    static int count2 = 1;
    
    void counter1(void) {
        printf("%d\n", count1);
    }
    
    static void counter2(void) {
        printf("%d\n", count2);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    定义头文件test.h

    int count2;
    void counter2();
    
    • 1
    • 2

    通过头文件或申明引入其它文件的函数、变量

    #include "stdio.h"
    #include "test.h"//通过头文件引入
    int count1; //申明引入
    void counter1();//申明引入
    int main() {
      printf("%d\n",count1);//1
      printf("%d\n",count2);//0
      counter1();//1
      counter2();//1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4.2 const常量修饰符

    如下表示指针p为常量,但该指针所指内存地址的值可以修改

    void f(const int* p) {
      int x = 13;
      p = &x; // 允许修改
    }
    
    • 1
    • 2
    • 3
    • 4

    改为修饰p,则指针地址可以修改,但不能修改值

    void f( int* const p) {
      int x = 13;
      p = &x; // 不能修改值
    }
    
    • 1
    • 2
    • 3
    • 4

    同时修饰则都不能修改

    4.3 register寄存器变量

    常用的变量可以register修饰,可能被存储在寄存器已提高访问速度,这种变量无法获取其地址

    4.4其它修饰符

    extern:函数默认有的修饰符,修饰变量则表明该变量多处共享,在其他地方已有初始化,这里就不要初始化了
    volitale:类似java
    restrict:只能用于指针,表明该指针是访问数据的唯一方式

    5、数组

       //顺序赋值+指定位置赋值,其它默认0,这里0、5、6、10被赋值,最大10,则数组长度为11
        int a[] = {1, [5] = 10, 11, [10] = 20};
        printf("allbytes:%d--anybytes:%d--length:%d\n",sizeof(a),sizeof(a[0]),sizeof(a)/sizeof(int));//allbytes:44--anybytes:4--length:11
        //数组指针也是首元素指针,可简化为数组名
        printf("%p--%p--%p--%p\n",&a[0],&a,a,&a[1]);//000000000022FE10--000000000022FE10--000000000022FE10--000000000022FE14
        
        //二维数组
        int b[2][3]={{1,2,3},{4,5,6}};
        printf("%p--%p\n",&b[0][0],b);//000000000022FDF0--000000000022FDF0
        //b[0]指向b[0][0],b[0]仍是一个指针,b[0] + 1指向b[0][1]
        printf("%d--%d--%d\n",**b,*(b[0]),*(&b[0][0]));//1--1--1
        
        //由于数组指针等价于数组名,故a[n] == *(a + n),故可以通过下标及指针来遍历数组
        //如下,注意数组指针不可变,可以将首元素指针值赋给新变量,让新变量+1来遍历数组
        int c[] = {11, 22, 33, 44, 55, 999};
        int* p = c;
        while (*p != 999) {
            printf("%d\n", *p);
            p++;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    6.字符串

        char str0[]="He" "ll""o";//可以有空格换行
        char str1[]="Hello";//编译器自动推断为5+"\0"结束标记字符
        char str2[6]="Hello";//同上,如果声明长度过多,会填充多个“\0”
        char str3[]={'H', 'e', 'l', 'l', 'o','\0'};//实际存储
        //str1=str2;数组类型修改指向的
        //str4是指向常量池,故不能修改,str4[0]='a'报错;而通过字符数组声明的字符串可以修改;
        // 但前者可以指向另一字符串;后者不可以,字符数组申明后就指向固定地址,只能修改地址上的值
        char* str4="Hello4";
        str4=str2;//指针方式声明的字符串可以修改指向
        printf("%s",str4);
        // 第一维编译时自动确认为7,第二维字符数组长度10过长,造成浪费,故这里声明为指针数组char* weekdays[]比较合适,每个指针指向第二维的字符串
        char weekdays[][10] = {
                "Monday",
                "Tuesday",
                "Wednesday",
                "Thursday",
                "Friday",
                "Saturday",
                "Sunday"
        };
    
        //字符串copy,拼接,比较,长度,格式化等api见string.h
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    7.内存管理

    • void 指针
      只有内存块的地址信息,暂时没有指定类型信息(没有类型无法解读内存块保存的二进制数据,*运算符取出它指向的值会报错)
    • 内存分配
      方法内局部变量占用的栈内存自动释放,全局变量堆内存需要手动释放,函数内部allloc分配在堆内存,可该返回指针地址,再函数外还可以获取该地址的值,不再使用需要使用free()手动释放;而直接声明的变量在栈内存,在出函数后自动释放返回的指针将指向NULL
    double* memTest() {
        double* temp = (double*) malloc(1024 * sizeof(double));
        if (temp == NULL) {
            // ,内存可能分配失败,指向NULL
        }
        *temp=12.4;
        return temp;
        //double temp=12.4;
        //return &temp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 其它api
      calloc:int* p = calloc(10, sizeof(int))会将所分配的内存全部初始化为0,等价于int* p = malloc(sizeof(int) * 10);memset(p, 0, sizeof(int) * 10);

      realloc:void* realloc(void* block, size_t size)缩放已分配的内存可能丢弃部分已初始化内存,新增扩大的内存不会自动初始化,失败返回NULL,一般返回原地址

      memcpy:void* memcpy void* restrict dest,void* restrict source,size_t n);restrict修饰符表示受限指针,不允许其它指针访问它的区域;可以取代strcpy()进行字符串拷贝,而且是更好的方法,不仅更安全,速度也更快,它不检查字符串尾部的\0字符

      memmove,memcmp内存移动,内存比较

    8.struct,union结构体

    struct类似高级语言的类,声明与赋值,union与struct类似,用在需要节省空间场景,它多个属性存在同一内存地址,后面写入的属性会覆盖前面的属性,其指针类型即当前属性类型。Union 结构占用的内存长度,等于它内部最长属性的长度,struct长度则是最长属性长度*属性个数,各个属性内存占用长度一致有利于提升访问速度。

    • 声明与赋值
      struct Car {
            char* name;
            float price;
            int speed;
            int time;
        };
        struct Car car = {"hongqi", .speed=122 };
        car.price=12.5F;
    //匿名struct
     struct {
            char f1;
            float f2;
        } mystruct;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 结构体不同于数组,结构体名并不是指针,直接作为方法入参传入的是副本,在方法修改结构体不影响方法外的结构体
    void changeCar(struct Car* car) {
      //等价(*car).price = (*car).price + 100;,对于 struct 变量指针,使用箭头运算符(->)获取属性
      car->price=car->price+100;
      
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 结构体案例-链表
    int main() {
        struct node {
            int data;
            struct node* next;
        };
        
        struct node* head;
        int size=sizeof(struct node);
        // 生成一个三个节点的列表 (11)->(22)->(33)并遍历
        head = malloc(size);
        head->data = 11;
        head->next = malloc(size);
        head->next->data = 22;
        head->next->next = malloc(size);
        head->next->next->data = 33;
        head->next->next->next = NULL;
        for (struct node *cur = head; cur != NULL; cur = cur->next) {
            printf("%d\n", cur->data);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    9.枚举

    枚举类型一般匿名访问时直接使用属性值即可,值只能是整型,默认值0,1,2,3…,可以在声明时自己赋值

    int main() {
        enum { V0,V1,V2 } ;
        enum { RED=0, YELLOW=4 } ;
        printf("%d %d", V0,YELLOW);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    10.预处理器 宏

    • 宏:常放文件开头,一个预处理语句就是一个宏,可以引用其他宏
    • 缺点:应该首先使用函数,它的功能更强、更容易理解。宏有时候会产生意想不到的替换结果,而且往往只能写成一行,除非对换行符进行转义,但是可读性就变得很差。
    • 优点:相对简单,本质上是字符串替换,不涉及数据类型,不像函数必须定义数据类型。而且,宏将每一处都替换成实际的代码,省掉了函数调用的开销,所以性能会好一些。另外,以前的代码大量使用宏,尤其是简单的数学运算,为了读懂前人的代码,需要对它有所了解
    #include "stdio.h"
    
    int main() {
    //1.定义宏
    #define SQUARE(X) X*X
        printf("%d\n", SQUARE(3 + 4));//#define将指定的词替换成另一个词,注意是原样替换,3+4*3+4=19
    //1.1#指定替换为字符串
    #define XNAME(n) "x"#n
        printf("%s\n", XNAME(4));// 输出="x""4"="x4"
    //1.2##粘合作用
    #define MK_ID(n) i##n
        int MK_ID(1), MK_ID(2), MK_ID(3);// 替换成int i1, i2, i3
    //1.3 不定参数的宏,__VA_ARGS__为C内置宏
    #define X(a, b, ...) (10*(a) + 20*(b)), __VA_ARGS__
        X(5, 4, 3.14, "Hi!", 12); //替换为(10*(5) + 20*(4)), 3.14, "Hi!", 12
    //2. 取消宏,在不知道上下文是否定义了该宏,最好undef一下
    #undef LIMIT
    //3.文件引入
    //标准库引入#include  ;引入用户文件使用相对或绝对路径 #include "../test/t1.h"
    //4.条件编译,可以通过编译参数指定 gcc -Dmyarg=1 main.c,但可能被源码的定义覆盖;其它编译参数 -I相当于#include -U相当于#undef
    //其它条件编译#ifdef(# if defined)...#endif,#ifndef(#if !defined)...#endif
    #define myarg 1
    #if myarg == 0
        printf("myarg0\n");
    #elif myarg == 1
        printf("myarg1\n");
    #else
        printf("myargx\n");
    #endif
    //5.指令用于让预处理器抛出一个错误,终止编译
    #if __STDC_VERSION__ != 201112L
    #error Not C11
    #endif
    //6.预定于宏(内置宏)
        printf("This function: %s\n", __func__);
        printf("This file: %s\n", __FILE__);
        printf("This line: %d\n", __LINE__);
        printf("Compiled on: %s %s\n", __DATE__, __TIME__);
        printf("C Version: %ld\n", __STDC_VERSION__);
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    11.api之io交互

        //可连续解读,会自动过滤空白字符,包括空格、制表符、换行符、其它不符合接收变量的符合等,返回成功解读个数
        int x;
        float y;
        int result = scanf("%d%f", &x, &y);
        printf("%d %f %d", x, y, result);//输入 " -13.45e12# 0" 输出-13 4.49999995e+11 2
        //字符串解读如果有空格、换行符、制表符等,没超过格式化长度,则按实际长度赋值;超过长度没遇到空格、换行符、制表符等且有限定格式化长度则按长度截断,之后的字符解读到下一个变量
        char str1[5];
        char str2[5];
        scanf("%2s %3s", str1, str2);
        printf("%s %s", str1, str2);//输入 a aaaa输出a aaa ;输入bbbb输出bb bb
        //赋值忽略符*
        int year;
        int month;
        scanf("%d%*2s%d%", &year, &month);
        printf("%d %d", year, month);//输入2022--12 输出2022 12
        //扫描字符串提前数据
        int i;
        int j;
        sscanf("22 343", "%d%d", &i, &j);
        printf("%d %d", i, j);//输出22 343
        //单个字符输入输出函数scanf()和printf() 整行输入输出gets()/puts()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    12.api之文件读写

    //fopen以各种模式打开文件进行读写操作,读写都是透过缓冲区批量读写,如读操作:批次较一定大小的数据读入到缓冲区,文件读取后指针指向下一字符,读完所有数据再重文件流读入批次数据到缓冲区
      FILE* fp = fopen("D://test.txt", "r");//不存在或没有权限返回NULL
      char* line;
      fgets(line,10,fp);
      printf("%s",line);
      fclose(fp);
      // FILE* freopen(char* filename, char* mode, FILE stream);将文件重新打开并关联到某个文件指针,stdin,stdout,stderr等也可以看作文件指针
       //关联到stdin输入流,可以将文件内存读进输入流,关联到输出流即可将内容输出到文件
        freopen("D://test2.txt","r",stdin);
        char  str[100];
        scanf("%s",str);
        fclose(stdin);
        printf("%s",str);
        //API例举
        FILE* fp4= fopen("D://test4.txt","a+");
        fputs("test4",fp4);
        char str4[100];
        fgets(str4,100,fp4);
        printf("%s",str4);
        fclose(fp4);
        int removeRes=remove("D://test4.txt");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    13. 编译

    • 多文件编译gcc -o someexe *.c
      文件过多编译耗时,可分为两个阶段:gcc -c *.c生成中间多个.o对象文件,再通过gcc -o someexe *.o 将它们连接为一个可执行文件。这样阶段一只需要compile变更文件。
    • 避免重复加载:头文件可能多个文件引入,导致重复编译,可以在头文件定义宏保证只加载一次,如下
    // File bar.h
    #ifndef BAR_H
      #define BAR_H
      int add(int, int);
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 编译工具make
      make可以根据makefile配置,判断源文件修改时间戳并只编译有变化的源文件后再生产可执行文件。

    14.程序参数

    • main函数入参
      ./someexe hello world 处理启动参数int main(int argc, char* argv[]) ,参数1为参数个数,包括程序名,argv[0]=“someexe”,另外argv[argc]=NULL,故如果通过指针遍历参数,可参考如下
    for (char** p = argv; *p != NULL; p++) {
      printf("arg: %s\n", *p);
    }
    
    • 1
    • 2
    • 3
    • 环境变量
      stdlib.h定义环境变量的获取方式getenv("xxxKey")
  • 相关阅读:
    《动手学深度学习 Pytorch版》 7.1 深度卷积神经网络(AlexNet)
    yolo增加Inner-IoU,一文搞定(Inner-SIoU,Inner-WIoU,Inner-EIoU,Inner-MPDIoU)
    ATA-8035射频功率放大器在通信领域的主要应用
    重新定义音乐创作:ChatGPT与未来音乐产业的融合
    校园网页设计成品 学校班级网页制作模板 dreamweaver网页作业 简单网页课程成品 大学生静态HTML网页源码
    《白帽子讲Web安全》知识点概括
    上海市培育“元宇宙”新赛道行动方案——虹膜识别技术为其助力
    印度网络安全:威胁与应对
    pytest学习和使用7-fixture参数scope作用域(范围)执行顺序
    离线安装wireshark2.6.10
  • 原文地址:https://blog.csdn.net/qq_39506978/article/details/126515397