基本类型

size_t 类型

一种类型别名,具体对应的可能是 unsigned long、 unsigned int 等,不确定。

printf:

  • 对应的占位符

    • %zu%zd
    • %ul 或 %u(没有 %zu 等时作为替代)

取值范围: [0, SIZE_MAX]

基本运算

运算符

% 取余

注意:运算符必须是整数

关系运算符 > < == 等

注意:不可连用

eg:

1
1 < x < 3                       /* 等价于: (1<x) < 3 */

幂(指数)运算

正确用法:

1
2
3
4
5
6
#include <math>                 /* 编译是,链接 m: gcc main.cpp -lm -o main */
int main(int argc, char *argv[])
{
    float result = pow(12,2);
    return 0;
}

注意:

  • 易错误使用: ^, 这是按位异或运算符,不是幂

问题

字面量

后缀

  • L 和 l

    • 整数: long int
    • 小数: long double

      • 注意:不是 double
    • 字符串

      • 宽字节字符串
  • LL 或 ll

    • 整数: long long
  • F 或 f

    • 小数: float
  • U 或 u

    • 整数: unsigned int
  • 组合

    • ull, ULL, LLU

输入输出

指示器

  • 类型

    • 错误指示器 Erro indicator
    • 文件尾部指示器 End-Of-File indicator
    • 位置指示器 Position indicator
  • 功能:

    • 输入输出库函数,通过设置这些指示器,来告知对应信息
  • 特点:

    • 全局变量,独立于库函数
  • 查看方法:

    • int ferror(FILE* pf): 错误指示器
    • long int ftell(FILE* pf): 位置指示器
    • int feof(FILE* pf): 文件末尾指示器 EOF
    • 注:通过返回值,获取指示器

常规输入输出

  • scanf

    • scanf("%3d%d",&a,&b)
    • 注:%3d 只取 3 个数字,多余的留待下一个输入
  • printf

    • printf("%x",a)
    • 注:

      • %x,%o 不会自动天剑前缀
      • %Ld, %Le:L 表示 double 类型
      • %ld:l 或 ll 表示 long int,long long int
      • %zd: z 表示 size_t 类型
      • %hd, %hu, %hx: h 表示 short, hu 表示 unsigned short
      • %m.nd:其中的“.n”

        • 对于%f,%e,%F:小数点后位数
        • 对于%g,%G:最大有效数字位数
        • 对于%s:最大输出的字符数
      • flags 标记

          • 左对齐
            
          • :显示正负号
        • 空格,0 :分别表示用空格或 0 当占位符
        • #:多功能

          • %#o:八进制,输出加 0
          • %#x:输出加 0x
          • %#f,%#e,%#g:小数点后没数字,也要打印一个 0
  • gets(str) 以空白符当截止符
  • puts(str) 自动把末尾‘\0’输出成换行

字符输入输出

  • 形式 A

    • putchar(char)
    • ch=getchar() 遇到汉字,可能需要两个连续的 getchar 接收
  • 形式 B

    • ch=getch()
  • 形式 C

    • ch=getche()
  • 辨析

    • ch=getchar() 按 enter 键,才能把前面键入的内容,存到 ch

      • 独特:它会通过多次调用来取走输入缓冲区(之前输入的)现有内容,用完之后再要求用户输入
      • 空格,‘\n’它也会取,记住:这是取字符,不是数值,不是字符串
    • ch=getch() 键盘键入字符,ch 立刻就得到
    • ch=getche() 与 getch 相同,

      • 除掉:你键入字符,它赋值给 ch,同时在还会它在屏幕输出

        • getch(), getche()需要调用 conio.h 头文件
        • 不可在 emacs 上调用 test.exe, 不然出错
  • 对文件流的字符输入输出

    • 特别

      • getc(FILE*)

        • 例子:getc(stdin) 相当于 getchar()

          • putc(int, FILE*)

             1
             2
             3
             4
             5
             6
             7
             8
             9
            10
            11
            12
            13
            
            #include <stdio.h>
            
            void main()
            {
                int ch;
                FILE *pfin = stdin; //定义一个文件指针,并指向标准输入设备(键盘)
                FILE *pfout = stdout; //定义一个文件指针,并指向标准输出设备(屏幕)
            
                printf("Enter a string: ");
                ch = getc(pfin); //使用getc()函数获取缓冲区中的第一个字符
                putc(ch, pfout); //使用putc()函数输出该字符
                putc('\n', pfout); //使用putc()函数输出换行字符
            }

文件输入输出

  • 对应头文件:<stdio.h>

开关文件

  • FILE* fp
  • fp=fopen(str_path, str_mode)

    • 例子

      • fp=fopen("d:\home\\text.c",“rb”)
      • fp=fopen("/home/text.c","rb")
      • char str[100]; fp=fopen(str,"rb")

        • 读写模式

           1
           2
           3
           4
           5
           6
           7
           8
           9
          10
          11
          12
          13
          
          "r"(只读)  输入数据,打开文本
          "rb"(只读)  输入数据,打开文本(二进制)
          "w"(只写)  输出数据,打开文本 文件不存在时,会建立新文件
          "wb"(只写)  输入数据,打开文本(二进制) 文件不存在时,会建立新文件
          "a"(追加)  末尾添加数据
          "ab"(追加)  末尾添加数据(二进制)
          
          "r+"(读写)
          "rb+"(读写)(二进制)
          "w+"(读写)文件不存在时,会建立新文件
          "wb+"(读写)(二进制) 文件不存在时,会建立新文件
          "a+"(读写)
          "ab+"(读写)(二进制)
  • fclose(fp)
  • freopen

    • *FILE *freopen( const char *path, const char *mode, FILE *stream );
    • 功能把 stdin,stdout 重定向到 path 指向的文件
    • 注意:

      • 要记得关闭 stdin,stdout,即 fclose(stdin)

         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        
        #include <stdio.h>
        int main()
        {
          int a,b;
          freopen("D:\\in.txt","r",stdin); //输入重定向,输入数据将从D盘根目录下的in.txt文件中读取
          freopen("D:\\out.txt","w",stdout); //输出重定向,输出数据将保存在D盘根目录下的out.txt文件中
          while(scanf("%d %d",&a,&b)!=EOF)
              printf("%d\n",a+b);
          fclose(stdin);//关闭重定向输入
          fclose(stdout);//关闭重定向输出
          return 0;
        }

判断文件开关是否成功

fopen(…) == NULL

文件打开模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
"r"(只读)  输入数据,打开文本 _“文件必须存在”_
"rb"(只读)  输入数据,打开文本(二进制) _“文件必须存在”_
"w"(只写)  输出数据,打开文本 文件不存在时,会建立新文件
"wb"(只写)  输入数据,打开文本(二进制) 文件不存在时,会建立新文件
"a"(追加)  末尾添加数据
"ab"(追加)  末尾添加数据(二进制)

"r+"(读写) _“文件必须存在”_
"rb+"(读写)(二进制) _“文件必须存在”_
"w+"(读写)文件不存在时,会建立新文件
"wb+"(读写)(二进制) 文件不存在时,会建立新文件
"a+"(读写)
"ab+"(读写)(二进制)
+ 注:
  + “r” 与 “r+” :模式文件必须存在
  + “a” 与  “a+”,“w” 与 “w+”:文件不存在,自动新建
  + “b” :二进制操作

文件读写函数

单个字符
  • fgetc(fp)

    • 从 fp 读取一个字符
  • fputc(char,fp)

    • 向 fp 写入一个字符
  • 失败判断:

    • 返回 EOF 即-1
字符串
  • fgets(str, size, fp)

    • 注意:

      • 实际读取的字符个数是 size-1, 因为 str 带的‘\0’不必读取,它自己添加
    • 失败判断:

      • 成功则返回 str 地址
      • 失败返回 NULL
  • fputs(str, fp)

    • 失败判断:

      • 成功返回 0
      • 失败返回-1
格式化读写文件
  • fscanf(fp, "%d%c", &a,%ch)

    • 失败判断:

      • 成功:返回读取参数个数,即上面的 a 和 ch,是 2
      • 失败:EOF, 或 读取错误
  • fprintf(fp, "%d%c", a, ch)

    • 失败判断:

      • 成功:返回输出字符个数
      • 失败:EOF,或 写错误
  • 实质分析

    • 把文件当成 stdin,stdout 了,其他都一样
二进制读写文件
整块读写
  • fread(buffer, size, count, fp)
  • fwrite(buffer, size, count, fp)
  • 注:

    • buffer:拿来存储数据的地址,可以是字符数组 str[100]
    • size:读取字节数
    • count:要读取数据项数

      • 注意:实际只要 size 与 count 的乘积,小于等于 buffer 的大小就好了 如:上 size*count<=100 即可
      • 一般吧 size=sizof(buff), count=1, 容易判断成功读写
    • fp:file pointer, FILE*
    • 注意:

      • 失败判断:

        • 成功:返回 count(上述)

          1
          
          fread(&t,sizeof(Node),1,fp)==1 //判断读写成功
          
        • 失败:feof, ferror,读写错误,EOF
        • size=0,或者 count=0,什么也不做
整数读写,二进制
  • int getw(FILE+ fp)

    • 从 fp 读取一个整数
    • 异常判断:

      • 正常返回对应整数
      • 错误:EOF(-1),用 feof()或 ferror()判断文件结束还是出错
  • int putw(int n, FILE* fp)

    • 从 fp 写入

文件定位

恢复默认

  • void rewind(FILE* fp)

    • 功能:把文件重新指向开头位置

随机定位

  • int fseek(FILE* fp, long offset, int base_position)
  • 功能:把文件指针 fp 指向,以 base_position 偏离 offset 个字节位置
  • offset:偏移量
  • base_position:偏移基准

    • 三个基准

      项目解释
      SEEK_SET文件开头
      SEEK_CUR当前位置
      SEEK_END文件末尾
  • 例子:

    1
    2
    3
    4
    5
    6
    
    fseek(fp, 0L, SEEK_SET); // 定位至文件开始处
    fseek(fp, 10L, SEEK_SET); // 定位至文件中的第10个字节
    fseek(fp, 2L, SEEK_CUR); // 从文件当前位置前移2个字节
    fseek(fp, 0L, SEEK_END); // 定位至文件结尾
    fseek(fp, -10L, SEEK_END); // 从文件结尾处回退10个字节
    //注:"L"表示long int
    
  • 错误判断:

    • 正常调用:

      • 返回指针位置 int 型
    • 失败判断:

      • 返回-1, 表示定位失败

当前位置

  • long int ftell(FILE* fp)
  • 功能:返回当前位置 long int
  • 失败判断:

    • 失败返回-1L

其余定位函数

  • int fgetpos( FILE *fp, fpos_t *pos );

    • 返回值:成功返回 0,否则返回非 0
  • int fsetpos(FILE *fp, const fpos_t *pos);

    • 返回值:成功返回 0,否则返回非 0
    • 类型 fpos_t 实际是一个整数 integer

C 语言中的缓冲区

目的

  • 协调磁盘与程序速度的不同步,

    • 磁盘慢,程序快
    • 磁盘以块为单位读写,512KB 为一块
    • 程序一个字节一个字节地读写
  • 如此可以提升整体速度

输入输出类型

  • 输入缓冲区
  • 输出缓冲区
  • 注:输入输出各用一个缓冲区,不相同
常见输入输出类型
  • stdin
  • stdout
  • stderr

操作类型

  • 全缓冲

    • 在这种情况下,当填满标准 I/O 缓存后才进行实际 I/O 操作。
    • 全缓冲的典型代表是对磁盘文件的读写。
  • 行缓冲

    • 在这种情况下,当在输入和输出中遇到换行符时, 执行真正的 I/O 操作。这时,我们输入的字符先存放在缓冲区, 等按下回车键换行时才进行实际的 I/O 操作。
    • 典型代表是标准输入(stdin)和标准输出(stdout)。
  • 不带缓冲

    • 也就是不进行缓冲,标准出错情况 stderr 是典型代表, 这使得出错信息可以直接尽快地显示出来。
    • 代表 stderr
  • 缓冲区大小

    • 系统默认,标准输入输出缓冲区 512Byte 字节
    • C 语言,

      • stdio.h 定义

        • 其中由宏 BUFSIZ 规定大小
    • 自己设定
  • 相关函数:

    • setvbuf(), setbuf()

如何清空缓冲区(刷新)

  • 特定条件:

    1. 缓冲区满时
    2. 黄缓冲区,遇到 Enter 键
    3. 关闭文件
    4. 使用特定函数

      • fflush(fp)

与缓冲区 stdin 相关的几个函数

  • getchar()
  • getch()
  • getche()

流错误

  • 操作错误指示器 Error indicator
  • 文件末尾指示器 End-Of-File indicator

EOF

  • int feof(FILE *fp)

    • 功能:检查文件操作异常,是不是因为 EOF
    • 返回值:

      • 1,非零,true:是 EOF
      • 0, false:不是 EOF

检查错误,非 EOF

  • ferror
  • 原型:int ferror(FILE *stream)
  • 功能:

    • 检查是否发生错误, 即:检查错误指示器:Error indicator
    • 并返回错误序号
  • 返回值:

    • 非零,true:发生错误
    • 0,false:没有发生错误

清空错误

  • clearerr
  • 原型:void clearerr ( FILE * stream );
  • 功能:

    • 清空错误,
    • 把 Error indicator 设置为 0, 即重设
    • 重设 EOF indicator

自动清空错误

  • fseek
  • rewind
  • fsetpos
  • freopen

尝试读取 stdin 输入

参考:

例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int number;
char ch;
char *Prompt2 = "":
do {
  printf("%sEnter number :", Prompt2);
  Prompt2 = "Invalid input\n";  // Change Prompt2
  buffer char[50];
  if (fgets(buffer, sizeof buffer, stdin) == NULL) {
    Handle_EOF();
  }
} while (sscanf(buffer, "%d %c", &number, &ch) != 1);

解释:

  • 通过 sscanf() 试错

C 语言错误处理

错误序号

  • errno
  • 头文件:<errno.h>
  • 解释:

    • 最后错误号 Last error number
    • int 类型
    • 代表错误类型
    • 初始值:0,代表没错误
  • 修改:

    • 可被程序修改成几个特定的宏
  • 注意:

    • 调用输入输出库函数之前

      • 要把它 errno 置零
      • 原因:因为之前调用的程序,可能已经更改过它了
  • 几个特定宏:

    英文解释例子
    EDOMDomain error数学函数运算,定义域错误如:sqrt()
    ERANGERange error值域错误如:pow(),结果超出 double 最大表示范围
    strtod(),超出 double 表示范围
    EILSEQIlligal sequence多字节字符 character,
    给定的序列,不存在对应的字符编码

输出错误

  • perror
  • 头文件<stdlib.h>
  • 原型:void perror ( const char * str );
  • 功能:

    • 把上次的错误,取出并通过 stderr,输出
    • 输入方式:

      • 先输出参数 str,再输出具体错误内容
  • 注意:

    • 操作的 errno,通过 errno 获取错误信息

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      
      #include <stdio.h>
      
      int main ()
      {
          FILE *fp;
      
          fp = fopen("file.txt", "r");
          if( fp == NULL ) {
              perror("Error: ");
              return(-1);
          }
          fclose(fp);
      
          return(0);
      }

错误字符串

  • strerror strerror_r
  • 头文件:<string.h>
  • 功能:

    • 返回指向错误信息的字符指针 char* str
  • 原型:

    • char* strerror(int error_number)
    • char* _strerror(const char* strErrMsg)
    • wchar_t* _wcserror(int error_number)
    • wchar_t* __wcserror(const char* strErrMsg)
    • 注:

      • error_number: 错误代码
      • strErrMsg: 用户提供的错误信息

清爽代码

if else

  • 单行写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    if (pFile == NULL) perror ("Error opening file");
    else {
        fputs ("test",pFile);
        fflush (pFile);    // flushing or repositioning required
        fgets (mybuffer,80,pFile);
        puts (mybuffer);
        fclose (pFile);
        return 0;
    }

格式化输入输出

  • 头文件:<stdio.h>
  • scanf and printf
  • sscanf and sprintf

    • sscanf(char* buf, "%s%d", &s, &n)
    • sprintf(char* buf, "%s", string)
    • 功能:

      • 即:用字符串,代替 stdin,stdout 来操作
    • char* buf: 即用来代替 stdin,stdout 的字符串
  • snscanf and snprintf

    • snprintf(char* buf, int size, "%s", str)
    • 注:

      • size: 限制 char* buf 中只有 size 个字符用于输入输出
  • fprintf and fscanf

    • fprintf(FILE* fp, "%s", str)
    • 功能:

      • 用 FILE* fp,代替 stdin, stdout 来操作
  • vprintf and vscanf

    • 来源头文件<stdio.h>
    • 相关头文件<stdarg.h>
    • 原型:int vprintf ( const char * format, va_list arg );
    • 解释:

      • 把 va_list 类型(argument list 类型)参数列表变量 arg,用 format 中的格式输出
    • 注意:

      • 使用前提:先要掌握<stdarg.h>头文件中的所有宏,不然白费力,就是看不明白
  • vsprintf and vsscanf
  • vsnprintf and vsnscanf
  • vfprintf and vfscanf

头文件<stdarg.h>

  • va_list

    • 中文:variable argument list 函数参数列表变量类型
    • 解释:

      • 一种特殊类型,用来存放变量 variable argumnet list(即函数的参数表)
  • va_start

    • 原型:void va_start (va_list ap, paramN);
    • 解释:

      • 把 paramN 之后的(不包括它本身)函数参数,存放到 argument list 变量 ap 中
      • 用来标志,argumnet list 变量 ap,使用范围的开始
  • va_end

    • 原型:void va_end (va_list ap);
    • 解释:

      • 用来标志,argument list 变量 ap,使用范围的结束
  • va_arg

    • 原型:type va_arg (va_list ap, type)
    • 解释:

      • 取出 argument list 变量 ap 中的一个元素,并把它转换成类型 type,返回
  • va_copy

    • 原型:void va_copy (va_list dest, va_list src);
    • 解释:

      • 把 va_list src 中元素,复制到 va_list dest 中
 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
/* va_arg example */
#include <stdio.h>      /* printf */
#include <stdarg.h>     /* va_list, va_start, va_arg, va_end */

int FindMax (int n, ...)
{
    int i,val,largest;
    va_list vl;
    va_start(vl,n);
    largest=va_arg(vl,int);
    for (i=1;i<n;i++)
    {
        val=va_arg(vl,int);
        largest=(largest>val)?largest:val;
    }
    va_end(vl);
    return largest;
}

int main ()
{
    int m;
    m= FindMax (7,702,422,631,834,892,104,772);
    printf ("The largest value is: %d\n",m);
    return 0;
}

二进制用途

& 按位与

把某一位置为 0

num & 0000 11111 把前四位置为 0,取出后四位不便

取特定位的数值

实例如下

判断奇偶书

if (num % 2 = 0) ------> if(num & 1 = 0) 注解:num & 1 –> num & 0b000001 即取出最末位的数值。

把特定位置零

一体两面,与上面的例子是,同一件事的另一个看待问题的方式。

清零

num & 0000 0000

| 按位或

把某一位置为 1

num | 0000 1111 前四位保持不变,把后四位置为 1

^ 按位异或 xor

使特定位数值翻转

num ^ 0000 1111 前四位保持不变,把后四位数值翻转,0变为 1,1 变为 0 eg:

1001 0110 ^0000 1111


1001 1001

与 0 相^异或,保持不变

与 1 相^异或,数值翻转

交换整数的数值,不需要临时变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int a, b;
a = 2;
b = 3;

// swap value
a ^= b;
b ^= a;
a ^= b;

//now
// a ----> 3
// b ----> 2

异或是可以逆运算的

即: c = a ^ b

则 (1) b = c ^ a, (2) a = c ^ b

异或满足交换律

即: c = a ^ b

则: (1) b = c ^ a = a ^ c, (2) a = c ^ b = b ^ c

注:通过这里的逆运算规律,和交换律,即可推出证明上面的交换数值算法 是正确的。

<< 左移运算符

右侧补零

左侧

符号位,没有被占用时,不影响符号,相当于乘以 2

0000 1111 >> 2 —> 0011 1100 左移 2 位,没有改变符号位,相当于乘以 2 的平方

符号位,被占用,而且改变符号

0000 1111 —> 15 十进制 0000 1111 >> 4 —> 1111 0000 左移 4 位,符号位改变,应当做改变后的符号来解释 上面例子,变为负数,当补码解释 1111 0000 - 1 —> 补码 1110 1111 —> 原码 1001 0000 —> -16 十进制 注:char 类型,最左面一位是符号位,原码补码之间的转化,保持符号位 固定不变。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
char ch = 15;
printf("15<<4: %d\n", (char) (ch<<4));

for (int i=0; i <= 8; i++)
    printf("%d: %d\n", i, (char) (ch << i));

/* output

   15<<4: -16
   0: 15
   1: 30
   2: 60
   3: 120
   4: -16
   5: -32
   6: -64
   7: -128
   8: 0

,*/
当符号位改变后,在不变的接下来一段时间内,左移位仍然是相当于乘 2 的操作

上一小节的代码输出,显然符合本结论

>> 右移位操作

对于正数

符号位不变,左侧补零,右侧丢弃

15 —> 0000 1111 >> 1 —> 0000 0111

15 —> 0000 1111 >> 4 —> 0000 0000 —> 0 十进制

正数,右移位的最终结果是,最后只剩下 0
不只是简单的,相当于除以 2
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
char ch = 15;
printf("15>>4: %d\n", (char) (ch>>4));

for (int i=0; i <= 8; i++)
    printf("%d: %d\n", i, (char) (ch >> i));

/*

  15>>4: 0
  0: 15
  1: 7
  2: 3
  3: 1
  4: 0
  5: 0
  6: 0
  7: 0
  8: 0

,*/

对于负数

符号位保持不变,左侧补一,右侧舍弃,

-16 —> 1111 0000 >> 1 —> 1111 1000

-16 —> 1111 0000 >> 4 —> 1111 1111 —> -1 十进制

负数,右移位的最终结果是,最后所有位都是 1,即-1 的补码,-1

不只是相当于,简单的除以 2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
char ch = -15;
printf("-15>>4: %d\n", (char) (ch>>4));

for (int i=0; i <= 8; i++)
    printf("%d: %d\n", i, (char) (ch >> i));

/*

  -15>>4: -1
  0: -15
  1: -8
  2: -4
  3: -2
  4: -1
  5: -1
  6: -1
  7: -1
  8: -1

,*/

类型转换

负数赋值给无符号型

1
2
3
4
5
6
7
8
9
unsigned char chr = -16;

printf("-15--> %d", chr);

/*

  -15--> 240

,*/

-16 —> 1111 0000 —> 直接当成无符号型解释,最左面一位,当成普通 数值解释,变成正数,1111 0000 —-> 正数 240

二进制数值内存中的数值保持不变,直接强行解释成正数

补码

设 4 位的有符号型

decimalbinary
原码补码
00000
10001
20010
30011
40100
-110011111
-210101110
-310111101
-411001100

加法

正数 加 正数

1 + 1 —-> 0001 + 0001 —> 0010 —> 2(10 进制)

正数 加 负数,按位相加(补码形式)

1 + (-1) —-> 0001 + 1111 —> 按位相加 —>

0001

1111


10000 —> 溢出最左侧的 1 —> 0000 —> 0

负数 加 负数

(-1) + (-1) —> 1111 + 1111—> 按位相加 —->

1111

1111


11110 —-> 溢出最左侧的 1 —> 1110 负数补码 —> 负数原码 1010 —> -2(10 进制)

综上:使用补码形式的加法,能够解决所有的加减法问题,(只需要按位相加即可)

动态链接库

参考: https://www.cnblogs.com/zuofaqi/p/10440754.html

linux 制作动态链接库

Just one file

1
2
3
4
5
6
# file: demo.h demo.c ---> demo.o ---> libdemo.so
gcc -fPIC -c demo.c -o demo.o    # ==> 编译
gcc -shared demo.o -o libdemo.so # ==> 链接

# or just one line
gcc -fPIC -shared -o libdemo.so demo.c  # 编译与链接,一起进行

More than one file

1
2
3
4
5
6
7
8
9
# file: add.h substract.h  add.c substract.c ---> add.o substract.o
# ---> libmath.so
gcc -fPIC -c add.c -o add.o
gcc -fPIC -c substract.c -o substract.o
gcc -shared -o libmath.so add.o substract.o


# or just one line
gcc -fPIC -shared -o libmath.so add.c substract.c

linux 使用动态链接库

链接

1
2
# link libmath.so into main.cpp ---> executable main.out
gcc -o main.out  main.o -L/path/to/your/lib -lmath

查看使用了哪些动态链接库

1
ldd main.out

使用命令 ldd

查看动态链接库或可执行文件本身
  1. 使用 nm 命令
  2. 可以查看文件中的符号信息
 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
0000000000201028 B __bss_start
0000000000201028 b completed.7594
         w __cxa_finalize@@GLIBC_2.2.5
00000000000005a0 t deregister_tm_clones
0000000000000630 t __do_global_dtors_aux
0000000000200e08 t __do_global_dtors_aux_fini_array_entry
0000000000201020 d __dso_handle
0000000000200e18 d _DYNAMIC
0000000000201028 D _edata
0000000000201030 B _end
00000000000006b4 T _fini
0000000000000670 t frame_dummy
0000000000200e00 t __frame_dummy_init_array_entry
0000000000000748 r __FRAME_END__
0000000000201000 d _GLOBAL_OFFSET_TABLE_
         w __gmon_start__
00000000000006cc r __GNU_EH_FRAME_HDR
0000000000000548 T _init
         w _ITM_deregisterTMCloneTable
         w _ITM_registerTMCloneTable
0000000000200e10 d __JCR_END__
0000000000200e10 d __JCR_LIST__
         w _Jv_RegisterClasses
         U puts@@GLIBC_2.2.5
00000000000005e0 t register_tm_clones
0000000000201028 d __TMC_END__
00000000000006a0 T _Z5printv   # 原代码中有print 函数

关于找不到动态链接库文件

如上述例子中: libmath.so

解决方法
  1. 使用环境变量

    1
    
    LD_LIBRARY_PATH=your_lib_path ./main.out
  2. 复制动态链接库到,系统的动态链接库目录,或做符号链接

    • /usr/lib 实测有效
    • /usr/local/lib 实测无效
  3. 修改/etc/ld.so.conf 文件

    1. 把你的动态链接库目录,添加到/etc/ld.so.conf 文件
    2. 运行命令 ldconfig
    3. 现在就可以运行你的程序./main.out

Windows 动态链接库

注: 根据博客整理,没有测试是否有效。 https://blog.csdn.net/qq_33757398/article/details/81545966

特殊修饰符

1
2
3
4
5
extern "C"

_declspec(dllexport)

_declspec(dllimport)
  1. 在函数定义的地方 加上: extern "C" _declspec(dllexport)
  2. 在函数声明的地方 加上:extern "C" _declspec(dllimport)
  3. 头文件

    • 在制作动态链接库时的头文件不需要独立 dllimport
    • 在使用动态链接库时的头文件必须有 dllimport
  4. _declspec 与 dllexport 等是 MFC 中的内容

头文件(.h)

1
2
3
4
5
6
// file: func.h
#pragma once

extern "C" int SquareSum(int a, int b);

extern "C" int SumSquare(int a, int b);

实现文件(.c)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//file: func.cpp
#include "func.h"
#include "stdafx.h"

extern "C" _declspec(dllexport) int SquareSum(int a, int b)
{
    return (a*a + b * b);
}

extern "C" _declspec(dllexport) int SumSquare(int a, int b)
{
    return ((a + b)*(a*b));
}

调用动态链接库

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//file: main.cpp
#include <iostream>
using namespace std;
#include "func.h"

#pragma comment(lib,"DynamicLib32.lib")

int main()
{
    int a = 34;
    int b = 32;
    cout << SquareSum(a, b) << endl;
    cout << SumSquare(a, b) << endl;

    system("pause");
    return 0;
}

注意事项

  1. 32 位动态链接库,64 位不能混用
  2. 复制过来,“.dll”文件和同名“.lib”文件

Python/C API

python 与 C 互相调用

  1. 给 python 写 C 扩展
  2. 把 python 嵌入到 C

在 C 中调用 python

工具

  • PyRun_

    • PyRun_SimpleString("python expression")
    • PyRun_SimpleFile

实例

  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
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#define PY_SSIZE_T_CLEAN

#include <python3.6m/Python.h>
#include <unistd.h>

double call_func(PyObject *func, double x, double y) {
  PyObject *args;
  PyObject *kwargs;
  PyObject *result = NULL;
  double retval = 0;

  // Make sure we own the GIL
  PyGILState_STATE state = PyGILState_Ensure();

  // Check if the given func is a proper callable
  if (!PyCallable_Check(func)) {
    fprintf(stderr, "call_func: expected a proper callable\n");
    goto fail;
  }

  // build arguments for python func
  args = Py_BuildValue("(dd)", x, y);
  kwargs = NULL;

  // call the python function
  result = PyObject_Call(func, args, kwargs);
  // decrement ref, of no use variables;
  Py_DECREF(args);
  Py_XDECREF(kwargs);

  // exception
  if (PyErr_Occurred()) {
    PyErr_Print();
    goto fail;
  }

  // verify python return value
  if (!PyFloat_Check(result)) {
    fprintf(stderr, "call_func: didn't return float");
    goto fail;
  }

  // convert return value form pyobject to c type
  retval = PyFloat_AsDouble(result);

  Py_DECREF(result);

  // release gil
  PyGILState_Release(state);
  return retval;

  // goto src block
fail:
  Py_XDECREF(result);
  PyGILState_Release(state);
  abort();
}

PyObject *import_name(const char *modname, const char *symbol) {
  PyObject *u_name, *module;
  u_name = PyUnicode_FromString(modname);
  printf("Before import\n");
  module = PyImport_Import(u_name);
  if (!module) {
    printf("Failed to import module: %s\n", modname);
  }
  Py_DECREF(u_name);

  return PyObject_GetAttrString(module, symbol);
}

PyObject *py_call_func(PyObject *self, PyObject *args) {
  PyObject *func;

  double x, y, result;
  if (!PyArg_ParseTuple(args, "Odd", &func, &x, &y)) {
    return NULL;
  }
  result = call_func(func, x, y);
  return Py_BuildValue("d", result);
}

int main(int argc, char **argv) {
  printf("Hello Py2C");

  PyObject *my_fun;

  Py_Initialize();

  printf("argc: %d, argv[0]: %s\n", argc, argv[0]);
  chdir("..");

  PyRun_SimpleString("import sys");
  PyRun_SimpleString("sys.path.append('.')");

  my_fun = import_name("sayfun", "pow");

  double result = 0;
  result = call_func(my_fun, 2, 3);
  printf("result of sayfun.pow(): %f", result);

  Py_DECREF(my_fun);

  Py_Finalize();
  return 0;
}

/* PyObject* pow_fun; */
/* pow_fun = import_name("sayfun", "pow"); */

/* /\* double x = 3, y = 2; *\/ */

/* /\* printf("result: %f", call_func(pow_fun, x, y)); *\/ */

/* Py_DECREF(pow_fun); */

函数

修饰符

  1. extern: 标明原型函数来自其他文件

    • 注意:函数证明(这里说的不是定义)默认就是 extern
  2. static: 表示函数 只可以 在当前文件使用

const

限制指针

  1. 限制指向的值: const int* p

    • 即,常量指针
  2. 限制指向的变量: int* const p

    • 即,指针常量

记忆:

    • 表示值, const int* 限制值

数组

列表初始化

  1. 长度小于数组长度的处理

列表小于数组长度时,长度不足的部分被初始化为 0

例子:

1
2
3
int a[5] = {22, 37, 3490};
// 等同于
int a[5] = {22, 37, 3490, 0, 0};
  1. 随机元素的赋值

例子:

1
int a[] = {[2] = 6, [9] = 12};

variable length arrary 变长数组

注意:这个特性,C++ 编译器不一定支持,C++ 推荐使用 vector

特点:

  • 运行时声明长度,不再必须是常量

数组复制

把 a 复制到 b:通过 string.h 中的 memcpy(a,b,size) 完成

函数和数组

数组作为参数

  1. 定长数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    /* 一维数组 */
    int sum_array(int a[], int n) {
      // ...
    }
    
    /* 二维数组 */
    int sum_array(int a[][4], int n) {
      // ...
    }
  2. 变长数组

数组字面量作为参数

1
2
3
4
5
6
// 数组变量作为参数
int a[] = {2, 3, 4, 5};
int sum = sum_array(a, 4);

// 数组字面量作为参数
int sum = sum_array((int []){2, 3, 4, 5}, 4);

方法:

  • 做数组类型转换

unicode 和 编码格式

参考:

C 语言使用编码格式解释:

  1. C 语言没有功能齐全的字符编码库
  2. 通过 locale.h 中的 setlocale(category, name) 设置本地编码,类似 linux locale
  3. C 中的 char* 相当于 python 中的 bytes, 即只支持 ASCII 字符

    • 如果包含 非 ASCII 字符 就无法处理了
    • 需要借助别的工具把其 unicode 字符串转换成多字节的 char* 类型字符转,例如: wcstombs()
  4. 如果 locale 不是 "C" (C 语言内部默认 locale, 即 ASCII 英语字符集), char* 实际存储的是多字节 bytes, 编码即 locale 指定的编码格式

  5. 内部 wchar_t* 使用的编码,由系统和编译器(gcc)决定

编码控制和转换

控制方法:

  1. 通过 locale.h, setlocale 控制
  2. 作用范围: char 和 char* 类型以及相关函数

转换方法:

  1. 使用 stdlib.h 中 wcstomb() 等工具,把 wchart_t* 类型方法转换成 chart* 类型

  2. mbstowcs() 把 char* 转换成 wchart_t*

编码使用范围

  1. 注意区分 语言内部编码语言外部编码
  2. 语言内部编码

    • char*

      • 编码格式不固定

        • 由系统和编译器决定
    • wchar_t*

      • 使用 unicode 编码
      • Windows

        • 使用 utf-16, BMP 以外的字符,需要两个 wchart_t 单元表示一个字符
      • Linux 和 MacOS

        • 使用 utf-32, (和 UCS-6 一致,等价于 unicode),一个字符,一个 wchart_t
  3. 语言外部编码

    • 范围:

      • 文件,stdin, stdout 等外部存储
      • 举例: cmd 和 powershell 通过 chcp 936 改变 stdin stdout 编码格式

作废笔记:

内部编码
+ locale 为 "C" 时,或只使用 ascii 字符时,默认 ascii 编码
+ locale 为其 gbk 等非 "C"(英语语言) 等时,使用 locale 设定编码,多字节表示一个字符

locale 和 编码

  1. locale 影响的是 char* 相关库函数
  2. locale 不决定 char* 的编码格式
  3. locale 处理的是本地化函数的结果,例如:千位分隔符、日期时间格式、货币符号等等

  4. locale 中的编码, eg: en_US.UTF-8

    • 这里的编码说明的是程序外部环境(编码)情况
    • 代码中 char str[] 中存储的字符编码由 editor 第一时间决定
  5. 文件编码的读取和 locale 无关,改变 locale 也不能适配文件编码的读取

bytes 类型

使用 chart* 表示

unicode 类型

使用 wchart_t* 表示

源码的编码处理

参考:

流程:

  1. 源码文件自己有一个编码格式:A
  2. 编译器解码代码文件有一个编码格式:B(源字符集)
  3. 编译器把代码制作成二进制机器码使用编码格式:C(执行字符集)

控制编码格式方法:

  • 编辑器控制代码文件编码格式(A)
  • gcc 控制 B 和 C 的方法:

    1. 机器码编码格式指定(C)

      1
      
      -fexec-charset=UTF-8
    2. 源码文件编码格式指定(B)

      1
      
      -finput-charset=GBK

变量修饰符

extern

作用:

  • 修饰函数声明或全局变量:这是跨文件的函数或变量
  • 修饰函数内的局部变量:这个变量是外部变量(非内部定义的局部变量),每次使用从函数外部获取它的值

用途:

  1. 修饰跨文件变量

    • 不可作用于 赋值 的变量定义并初始化语句,否则无效

      1
      2
      3
      4
      5
      6
      
      extern int i; //正确用法
      
      // 不规范用法
      extern int j = 1;
      // --> 等价于
      int j = ;
  2. 修饰函数声明

    • c 语言中,函数证明默认自带 extern
    • 因此,函数不用使用 extern 也可以, 即使是跨文件函数

volatile

背景介绍:

  1. 优化问题:compiler 会优化代码,长久不变的()会被读入 cache, 并且长久只使用这个值。
  2. 实际需求:这个变量但是可能很长时间以后突然改变,而我们需要监测的就是这个突然变化,由于 compiler 的 优化, 我们无法获取到这个最新的值,造成问题的产生 参考:declaration - Why is volatile needed in C? - Stack Overflow

    • 并行、并发任务可能会触发这个问题,例如:在另一个线程改变了一个全局变量,一个硬件改变了一个变量
  3. volatile 就是用来解决这种问题的,它强制 compiler 不要优化被修饰的变量,每次读取变量时, 使用的都是最新的值

作用:

  • 放置 compiler 对变量进行优化处理(缓存)
  • 强制读取最新的变量值(原始位置),而不是从缓存中读取过时数据
  • 对于写也是一样的道理,修改的是原始位置的值,而不是缓存中的临时值

restrict

参考:

作用:

  • 放置其他通过指针修改被指向的值
  • 设定当前指针是访问变量的唯一方式

例子:

1
int* restrict pt = (int*) malloc(10 * sizeof(int));

多线程

threads.h – C 语言标准库并发

参考: