c语言基础

c语言环境搭建

  • 安装visual studio 2019
  • 安装 MinGW (模拟 posix环境,可以安装 msys2)
  • 安装 wsl

安装 msys2

1
2
3
4
pacman -Sy base-devel


pacman -S mingw-w64-x86_64-toolchain

环境配置 参考博客

我选择默认的全部安装,安装 mingw 大概会占用1G的空间

将 安装 mingw 的 bin 目录 配置到环境变量,这样就可以全局调用这些命令了,例如 gcc,g++ 等

如果你没有安装 mingw 的环境,和配置好环境变量,默认是无法直接命令行编译 c语言的文件的, 可以安装一些IDE,IDE 自带开发环境, 但是最好还是自己配, 这样 vscode 上可以快捷键 直接运行

[[content/post/13.软件使用总结/代码/msys2.md|msys2环境安装]]

1
2
3
4
5
6
7
8
#include <stdio.h>

int main(void) {
    printf("hello world\n");
    

    return 0;
}

c语言断点调试

安装 c/c++ 的插件,并且全局启用

查看生成的汇编指令

参考文章

在监视控制台输入命令 -exec disassemble /m main

最基本数据类型

类型 解释
short int 不低于2字节
int 4字节
long int 不小于 4字节
long long 8字节
float 单精度浮点(规定至少能表示6位有效数字)(10^-37,10^37)
double 双精度浮点

c语言的标准并没有给定标准,所以 类型长度不能确定

printf("%d\n",sizeof(short int))

1
2
3
4
5
6
7
8
int main(void)
{

    printf("%d\n", sizeof(short int));
    printf("%d\n", sizeof(int));
    printf("%d\n", sizeof(long int));
    return 0;
}

在 mingGW 下面,显示结果是 2 4 4

常用头文件

limits.h

转义字符

字符类型 解释
\0 八进制0
‘\61’ 八进制的61
‘\x31’ 16进制的31
‘\b’ 退格,backspace
‘\r’ 回车
\t 制表符
' 字面量
" 字符的字面量

要表示 中文,就需要使用宽字符 wchar_t

1
2
3
4
5
6
7
8
#include <stdio.h>
int main(void)
{
    wchar_t a = L'中';
    char * str = "中国";
    printf("aaa %c, %s\n",a,str); 
    return 0;
}

vscode 下查看 c语言内存 布局

参考博客

宏和只读变量

宏在编译的时候会直接对变量替换,所以内存中不存在这个宏定义

1
2
3
4
#define color 1

#undef color
// undefine 可以取消定义,后面的行就无法使用这个了

函数基础

c语言规范命名

函数的原型

1
2
3
4
5
6
7
8
9
main() {


}
// 这种是经典 c语言的写法了
int main() {}
//表示没有参数
int main(void) {}
// void 参数表示 什么都没有

下面 可以运行一下这个代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdio.h>

void test1() {
    puts("----");
}

int main(void)
{
    test1(1,23);
    test1("aaa"); 
    return 0;
}

c++ 编译这个代码会报错,但是 纯c语言 是不会报错的。

==黑魔法== : 可以直接去函数的栈上获取参数

c语言 可以设置 函数原型【在头文件只是声明函数的名字, 之后引入函数的实现】

1
2
3
4
5
6
7
8
#include <stdio.h>
int Add(int a, int b);
int main(void)
{
    int c = Add(1, 2);
    printf("%d\n", c);
    return 0;
}

如果函数参数列表声明都没有,要写 void 上去,方便编译的时候出现问题

1
void Add(void)

变长参数

printf 就是用了一个变长参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
__mingw_ovr
__attribute__((__format__ (gnu_printf, 1, 2))) __MINGW_ATTRIB_NONNULL(1)
int printf (const char *__format, ...)
{
  int __retval;
  __builtin_va_list __local_argv; __builtin_va_start( __local_argv, __format );
  __retval = __mingw_vfprintf( stdout, __format, __local_argv );
  __builtin_va_end( __local_argv );
  return __retval;
}

代码演示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

#include <stdio.h>
#include <stdarg.h>

void handle_arg(int arg_cnt, ...)
{
    //定义变长参数
    va_list args;
    int i;
    va_start(args, arg_cnt);
    for (int i = 0; i < arg_cnt; i++)
    {
        int a = va_arg(args, int);
        printf("%d: %d\n", i, a);
    }
    // 清理变长参数内存
    va_end(args);
}
int main(void)
{

    handle_arg(3, 1, 2, 3);
    return 0;
}

c语言编译过程

graph LR 源代码-->预处理器--宏替换后的源代码-->编译器--中间文件-->链接器-->可执行文件
1
2
3
4
5
6
7
8
int __cdecl printf(const char* fmt,...);

int main(void)
{

    printf("aaaa %d\n",1); 
    return 0;
}

静态链接和动态链接库

动态链接不需要打包到可执行文件,放在特定的路径上,运行的时候加载进来。(可以共享) 静态链接则是编译的时候就确定了,把需要用到的东西全部加载进可执行文件里面。(没法共享)

1
2
3
4
# 编译静态链接的方法
gcc -c -o main.o main.c
ar rcs libmain.a main.o
# libmain 就是一个静态链接库

程序本身运行的时候也会占用内存空间,多个程序共享动态链接库可以减少内存占用

1
2
3
4
5
6
7
# ldd 查看依赖哪些动态链接库
which ldd
ldd /usr/bin/ls
ldd a.out
# 查看可执行程序依赖哪些文件

gcc -shared -fPIC -wl,--out-implib main.c  -o libmain.dll 
1
2
3
4
5
6
# mingw 上使用 ldd命令
$ ldd a.exe
        ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7ffb2fbd0000)
        KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7ffb2e3c0000)
        KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7ffb2d450000)
        msvcrt.dll => /c/WINDOWS/System32/msvcrt.dll (0x7ffb2e320000)

自定义头文件

1
2
#include "../include/factor.h"
// 注意,使用路径的方式 必须用引号

使用双引号的时候,会从本地路径去查找,找不到再从搜索路径查找, gcc 编译的时候 可以在 上面加参数添加搜索路径

1
2
3
4
5
#include "include/factor.h"
int main() {
    printf("aaa\n");
    return 0;
}

常用的宏

解释
FILE 所在文件
LINE
FUNCTION 所在的函数

数组

数组的声明和初始化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
// #include "io_utils.h"

#define ARRAY_SIZE 10
int main(void)
{
    int a[ARRAY_SIZE]={0}; // 后面 {0} 是初始化列表,如果只会声明 int a[10], 是不会赋值的
    // 使用初始化列表后,后面的默认全部填为0
    // 在 局部函数声明的数组,是在栈上开辟的内存

    for (size_t i = 0; i < ARRAY_SIZE; i++)
    {
        // a[i] = 1;

        printf("%d\n",a[i]); 
    }
    
    printf("%d -- %d\n",a,&main);
    return 0;
}

打乱数组

 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
#include <stdio.h>
#include <stdlib.h>

#define LEN 100
int main(void)
{
    int a[LEN] = {0};
    for (size_t i = 0; i < LEN; i++)
    {
        a[i] = i + 1;
        // printf("%d\n",a[i]);
    }

    srand(time(NULL));

    for (size_t i = LEN - 1; i > 0; i--)
    {
        int pos = rand() % i;
        int t = a[pos];
        a[pos] = a[i];
        a[i] = t;

    }
    for (size_t i = 0; i < LEN; i++)
    {
        printf("%d\n", a[i]);
    }

    return 0;
}

实现快速排序

快速排序主要有2种经典的切分算法

  • Lomuto partition scheme
  • Hoare partition scheme

Lomuto partition scheme

该算法的思路是: 准备一个队列, 选择序列最后一个数作为基准,从头到尾遍历这个序列,小于 pivot的数放都放入左边,左边的队列长度+1

遍历完之后 队列中的所有数都小于 pivot,不是 队列中的数都大于等会 pivot

 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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <stdio.h>
#include <stdlib.h>

#define LEN 100
int swap(int a[],int l,int r) {
    int c = a[l];
    a[l] = a[r];
    a[r] = c;
}
int partition(int *array, int low, int high)
{
    int pivot = array[high];
    int p = low;
    for (int i = low; i < high; i++)
    {
        if (array[i] < pivot)
        {
           swap(array,i,p++); //加入 队列,并且队列长度+1 , 
           // 左边 <=p  位置的 就是 比 pivot 小的队列, 右边就是 比 pivot 大于或者等于的数
        }
    }
    swap(array,p,high);
    return p;
}

void quick_sort(int *array, int l, int r)
{
    if (l>=r) 
        return;
    int p = partition(array, l, r);
    quick_sort(array, l, p - 1);
    quick_sort(array, p + 1, r);
}

int main(void)
{
    int a[LEN] = {0};
    for (size_t i = 0; i < LEN; i++)
    {
        a[i] = i + 1;
        // printf("%d\n",a[i]);
    }

    srand(time(NULL));

    for (size_t i = LEN - 1; i > 0; i--)
    {
        int pos = rand() % i;
        int t = a[pos];
        a[pos] = a[i];
        a[i] = t;
    }
    quick_sort(a, 0, LEN - 1);
    for (int i = 0; i < LEN; i++)
    {
        printf("%d ,", a[i]);
    }

    return 0;
}

Hoare partition scheme

算法的思路是中间取出一个数作为基准元素, 两个指针从两边往中间靠拢,左边维护一个 小于 pivot的序列,右边维护一个 大于 pivot的序列

左右两个序列的边界我们称为 p 和 q, p和 q之间这是等于 pivot的序列,p和 q靠拢在一起的时候 返回 停留的位置

 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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//  c语言没有泛型编程,但是可以利用宏的方式来实现万能的写法

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

void shuffle(int *p, int len)
{
    srand(time(NULL));
    for (int i = len - 1; i > 0; i--)
    {
        int j = rand() % i;
        swap(p + i, p + j);
    }
}
int *partition(int *l, int *r)
{
    // pivot
    // queue < pivot ,    return queue.tail
    int pivot = *(l + (r - l) / 2);
    int *p = l;
    int *q = r;
    while (1)
    { //这里可以用 do while ,总之左右两边一定要先和   pivot 比较一遍
        while (*p < pivot)
            p++;
        while (*q > pivot)
            q--;
        if(p>=q) break;
        swap(p, q);
    }
    return p;
}

void quick_sort(int *l, int *r)
{
    if (l >= r)
        return;
    int *q = partition(l, r);
    quick_sort(l, q - 1);
    quick_sort(q + 1, r);
}
#define N 10
int main(void)
{
    int *a = malloc(sizeof(int) * N);
    for (int i = 0; i < N; i++)
        a[i] = i + 1;
    shuffle(a, N);
    quick_sort(a, a+ N-1);
    for (size_t i = 0; i < N; i++)
    {
        printf("%d,", a[i]);
    }

    return 0;
}

指针学习

指针的长度是多大?

32 位机器是4个字节, 64位机器是 8个字节

1
2
3
4
5
6
7
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    printf("%d -- %d\n",sizeof(int *),sizeof (long long *)); 
    return 0;
}

只读指针变量和只读变量指针

1
2
3
4
5
int *const cp = &a;// 不能修改指针的 引用
*cp = 333;// 可以修改指向的地址的内存

int const * const p = &b; // 只读
//禁止修改

动态内存分配

c语言允许 从堆区分配一块内存

  1. malloc
  2. calloc
    • calloc 会清空内存
  3. realloc
    • 不会清空原来的内存,而是尝试获取一块新内存

参考文档

常见问题

  • 忘记使用完毕之后释放内存
  • 使用了已经释放的内存
  • 使用了超出边界的内存
  • 改变内存的指针,导致无法正常释放

使用建议

  • 避免修改指向已经分配的内存的指针
  • 对于free 之后的指针主动改为 NULL
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>
int main(void)
{

    #define LEN 10    
    int *p = calloc(LEN,sizeof(int))

    for (size_t i = 0; i < LEN; i++)
    {
        p[i] = i+1;
        printf("%d\n",p[i]); 
    }
    
    
    free(p);    
    return 0;
}

用完指针需要 free 掉, 有些操作系统可能不会回收这块内存,你需要手动销毁返回内存给操作系统

 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
#include <stdio.h>
#include <stdlib.h>

void getArray(int **ptr,int len,int value) {
    *ptr = malloc(sizeof(int)*len );

    for (size_t i = 0; i < len; i++)
    {
        //括号的优先级比较高,先去地址
        (*ptr)[i] = value;
    }

}

int main(void)
{

#define LEN 10    
    int *array;
    getArray(&array,10,0);
   
    for (size_t i = 0; i < 10; i++)
    {
        array[i] = i+1;
    }
     //重新分配,数组内存不够的时候使用
    array = realloc(array,20*sizeof(int));
    
    
    for (size_t i = 0; i < 20; i++)
    {
        printf("%d,",array[i]); 
    }
        

    free(array);
    
    return 0;
}

函数指针

 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
#include <stdio.h>
#include <stdlib.h>

void getArray(int **ptr,int len,int value) {
    *ptr = malloc(sizeof(int)*len );

    for (size_t i = 0; i < len; i++)
    {
        //括号的优先级比较高,先去地址
        (*ptr)[i] = value;
    }

}

int main(void)
{

#define LEN 10    
    int *array;
    void (* fn) (int **ptr,int len,int value) = &getArray;
    // getArray(&array,10,0);
    fn(&array,10,0);
    //重新分配,数组内存不够的时候使用
    free(array);
    // 函数名 就是函数的地址, 地址解引用,还是函数的地址
    (*fn)(&array,10,0);

    free(array);
    printf("%d-- %p\n",fn == &getArray ,fn); 
    


    return 0;
}
写法 解释
int *f(int) 函数,返回 int*
int * (f(int)) 同上
int (*f)(int, double) 函数指针,返回int
int * (*f) (int, double) 函数指针,返回 int*

工具网站 cdecl

通过这个网站可以比较容易分析出类型

关键字和宏

1
2
typedef int boolean
typedef int Boolean

N种方式实现 swap方法

实现任意类型的变量交换内容

参考博客

 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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//  c语言没有泛型编程,但是可以利用宏的方式来实现万能的写法

#define swap1(a,b,type) { type temp=a;a=b;b=temp;   } 
#define iswap(a,b) do { typeof(a) t=a;a=b;b=t; }while(0)


// memory swap 
void mswap(void *l, void *r, size_t size)
{
    void *temp = malloc(size);
    if(temp!=NULL) {
        memcpy(temp,l,size);
        memcpy(l,r,size);
        memcpy(r,temp,size);
        free(temp);
    } 
}

int main(void)
{
    int a = 1;
    int b = 2;

//    mswap( &a, &b, sizeof(a));
    // swap1(a,b,int);
    typeof (a) c = 666;
    if(a) iswap(a,b); 
    else printf("error\n"); 
    printf("%d %d -- %d\n", a, b,c);

    return 0;
}

结构体

指针要用 -> 符号

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//  c语言没有泛型编程,但是可以利用宏的方式来实现万能的写法
int main(void) {
    struct Person {
        char *name;
        int  age;
    };
    struct Person p = {.name = "hello person", .age = 18};
    printf("%d\n", p.age); 

    typedef  struct Person Person; 
    Person p2 = {"is p2",15}; // c语言 必须要 用 struct Person ,除非 你用 typedef 声明类型
    printf("p2.name=%s--\n",p2.name); 
    struct {
         char *name;
    }teacher; //匿名结构体 ,没有名字,直接定义变量的类型 
    teacher.name = "hello world";
    printf("name = %s age=%d\n",teacher.name, (&p) -> age); 
    return 0;
}

最简单的写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//  c语言没有泛型编程,但是可以利用宏的方式来实现万能的写法
int main(void) {
    typedef struct {
        char *name;
        int age;
    }Student;

    printf("%d\n",sizeof(Student));
    Student u = {.name="student",.age=18};
    printf("name=%s,age=%d\n",u.name,u.age); 
    
    return 0;
}

代码打印

1
2
16 -- 8 -- 4
name=student,age=18

这里就出现了内存对齐的现象,理论上来说 8+4 得到12个字节才对, 结果使用了内存对齐,补充了4个字节

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
int main(void) {
    //设置对齐为2的倍数
    #pragma pack(2)
    typedef struct Noxx{
        char *name;
        int age;
    }Student;

    printf("%d -- %d -- %d\n",sizeof(Student),sizeof(char *),sizeof(int));
    Student u = {.name="student",.age=18};
    printf("name=%s,age=%d\n",u.name,u.age); 
 
    return 0;
}

参考文章

内存对齐规则:

  • 结构体对象大小是对齐大小的整数倍
  • 成员变量起始地址对齐一个数字 ,即取 $min(成员大小,对齐大小)$
1
2
3
4
5
6
7
8

class child {
    int age; //4
    char sex ; //1
    // char padding 1; 
    short height // 2  ,这里填充一个 padding 1, 起始地址是 min(2,4) => 2, 所以 height 前面要加一个 1的padding
};
//sizeof(child) ==  8

32 位cpu 4字节对齐, 64位CPU 8字节对齐

计算机每次读写一个字节块,例如,假设计算机总是从内存中取8个字节,如果一个double数据的地址对齐成8的倍数,那么一个内存操作就可以读或者写,但是 如果这个double数据的地址没有对齐,数据就可能被放在两个8字节块中,那么我们可能需要执行两次内存访问,才能读写完成。显然在这样的情况下,是低效的。所以需要字节对齐来提高内存系统性能。 在有些处理器中,如果需要未对齐的数据,可能不能够正确工作甚至crash,这里我们不多讨论

1
2
3
4
5
6
typedef struct {
    long long age; //4
    int aa;
}Child;
// 输出 16
printf("%d\n",sizeof(Child));     

联合体

联合体 共享变量的同一块内存, 下面 a,b,c 都共用一块内存,而不是分开的3个内存

1
2
3
4
5
union A {
   char a;
   int b;
   long long c;// sizeof(A) is 8
};

枚举

1
2
3
typedef enum FileFormat {
     PNG, JPEG,BMP
} FileFormat;

文件读写

 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
#include <stdio.h>

#include <io.h>
//  c语言没有泛型编程,但是可以利用宏的方式来实现万能的写法
int main(void) {
    FILE *file = fopen("./temp.txt","rb");
    if (file) {

        char buf[1024]={0};
        size_t buf_cnt = fread(buf,1,1024,file);
        if (buf_cnt <= 0) {
            fprintf(stderr,"error !!!! buf_cnt");
        }else {
            printf("%s\n",buf); 
        }




        fclose(file);
    }else {
        fprintf(stderr,"error open!!");
    }
 
    return 0;
}

小端序 和大端序

比如: 01,02,03,04

内存里面是按照小端序存的

0x04030201

我们的系统一般情况下是小端序,要证明是大小端序,我们可以用联合体实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdio.h>

 
int main(void) {
    typedef union {
        char c[2];
        short s ;
    } Person;
    Person value = {.s = 0x100};
    
    printf("%d %d, %d\n",value.s,value.c[0]==1,value.c[1]==1); 


    return 0;
}

字符串

方法 用法
strlen or strnlen 字符串长度
strcmp or strncmp 字符串比较 ,不能无限制比较
strchr 字符串查找,从左到右查
strrchr 字符串查找,reverse,从右到做查
strbrk string break ,按照多个字符查找
strstr 查找子串开始的位置
strcat 字符串拼接
strcpy(dest,src) ,or memcpy(dest,src,size) 字符复制或者内存复制
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    char *p = "aaa,bbb,ccc";
    printf("%s\n",strchr(p,'c')); 
    return 0;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    char *p = "aaa,bbb,ccc.bbb;cccde";
    do {
        p = strpbrk(p,",.;");
        if (p) puts(p) ,p++;
    }while(p);

    return 0;
}

时间

  • UTC 是世界调和时间,是国际时间的标准,不受时间影响
  • GMT 格林治时间,与UTC时间一致,但是我们说gmt的时候是说零时区的时间,它不是时间标准了。
  • Epoch ,一般翻译为纪元, 我们计算机程序都是从UTC 时间 1970年1月 1日0时0分0秒开始的一个整数值,这是unix计时方法,unix系统对c标准的扩展标准posix也采用这样的规定,因此这个起始时间称为unix epoch, 大多数编程语言java,js ,win系统上的c都采用unix epoch 等

时间类型:

  1. time_t, 表示从 epoch 开始计算的时间
  2. clock_t ,
  3. tm , 时间的结构体
  4. timespec , 可以查看毫秒纳秒等精确单位
  5. timeb , 与time_t类型类似,但是没有精确单位,只能用于计算秒数
 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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main(void)
{
    //打印时间的方法
    time_t t;// 获取日历时间
    time(&t);
    
    printf("%s", ctime(&t));//转字符串  Mon Jun 06 17:41:51 2022
    printf("%d\n",t);  //1654508511
    /* 
     struct tm {
    int tm_sec;
    int tm_min;
    int tm_hour;
    int tm_mday;
    int tm_mon;
    int tm_year;
    int tm_wday;
    int tm_yday;
    int tm_isdst;
  };
     */
    struct tm *info = localtime (&t);//转时间结构体 
    printf("%d-%d-%d %d:%d:%d\n",info->tm_year+1900,info->tm_mon+1,info->tm_mday,info->tm_hour,info->tm_min,info->tm_sec);


    return 0;
}

错误处理和 io

file access mode mean explain action
r read open file for read 如果不存在,就报错
w write create file for write 如果存在就消除内容,否则就创建文件
a append append file create new or write end
r+ read extended open file for read/write 不存在就报错error
w+ write extended create file for read/write create new
a+ append extended open file for read/write create new,or write in end
 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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main(void)
{

    // for (int i = 0; i < 10; i++)
    // {
    //     puts(strerror(i));
    // }
    printf("---- error 类型\n" );
    FILE *f = fopen("./app.txt", "w+");
    char buf[2048];
    setbuf(f, buf); // 设置缓冲区
    char stdbuf[2048];
    setbuf(stdout, stdbuf); // 设置stdout缓冲区

    fflush(stdout);// flush 缓冲区
    if (f == NULL)
    {
        perror("fopen");
        
    
        exit(1);
    }
    else
    {

        puts(strerror(ferror(f)));
        printf("%d\n",feof(f)); 
        fclose(f); // 关闭句柄
    }
    /*
    mod:
    r,w,a ,r+,w+,a+


    */

    return 0;
}

文件流缓冲区

graph LR dma --读取到io缓冲区--> 缓冲区 --cpu读取数据--> 复制到调用者传入的内存空间

读取文件

函数 用法
getchar(void) 从 stdin读取一个字符
int fgetc(FILE *s) 读取文件 字符
int getc(FILE *s) 读取一个字符

getchar 等价于 getc(stdin)

 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
43
44
45
46
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main(void)
{
    fprintf(stdout, "Hello, World!\n");
    fflush(stdout);
    while(1) {
        int i = getc(stdin);
        if (i == EOF) {
            break;
        }
        if(i == '0') break;  
        fprintf(stdout, "%c", i);
        // putc(i, stdout);
    }
    int i=0;
    do {
        fscanf(stdin, "%d", &i);
        printf(" number %d\n",i); 
    }while(0);

    printf("copy files\n");

    char *src = "main.c";
    char *dst = "main.c.bak";
    FILE *fsrc = fopen(src, "r");
    FILE *fdst = fopen(dst, "w+");

    char buf[1024];
    while(!feof(fsrc)) {
        int n = fread(buf, 1, 1024, fsrc);
        fwrite(buf, 1, n, fdst);
        fwrite(buf,1,n,stdout);
    }
    puts(ferror(fsrc));
    // while(!feof(dst)) {
    //     int n = fread(buf,1,1024,fdst);
    //     fwrite(buf,1,n,stdout);
    // }

    fclose(fsrc);
    fclose(fdst);
    return 0;
}

格式化文本输出

| scanf| 读取屏幕字符| | fscanf(file,fmt,…| 从文件中读取 | | sscanf(char *buf,fmt,…) |从字符串中读取 |

创建 Cmake项目

参考文章

Conan 管理依赖

网址

通过 python的pip 安装 conan

依赖搜索

1
2
3
pip install conan

conan search libcurl

在根目录下 创建 conanfile.txt

1
2
3
4
[requires]
libcurl/1.0.0
[generators]
cmake
1
connan install .. 

cpp调用 c语言的代码

因为 cpp 和 c中 符号命名规则是不一样的,不能直接 调用 c函数的代码 需要在 .h 头文件的 函数声明上面 加 extern “C” 这个语句

1
2
3
4
5
6
7
8
9
#ifdef __cplusplus
extern "C" {
#endif

int fib(int n);

#ifdef __cplusplus
};
#endif

java 调用 c

java 有 jni (java native interface )的机制去加载 c的方法