【C1】基本数据类型、流程控制



2021年11月20日    Author:Guofei

文章归类: C    文章编号: 10001

版权声明:本文作者是郭飞。转载随意,但需要标明原文链接,并通知本人
原文链接:https://www.guofei.site/2021/11/20/c1.html


基本数据结构

一个二进制位是一个bit,8 bit=1 Byte

c 本身不规定变量类型的范围,跟系统有关,我的 MacBook 如下:

修饰符 数据类型 占用(字节) 取值范围
signed short int 2 -32768 到 32767 (-2^15 ~ 2^15-1)
int 4 -2147483648 到 2147483647 (-2^31 ~ 2^31-1)
long int
long long int
8
unsigned short int 2 0 到 65535 (0 ~ 2^16-1)
int 4 0 到 4294967295 (0 ~ 2^32-1)
long int 8
float 4 1.4E-45 ~ 3.4E+38,-1.4E-45 ~ -3.4E+38
duble 8 4.9E-324 ~ 1.7E+308, -4.9E-324 ~ -1.7E+308
char 1 -128~127
unsigned char 1 0~255
#include <stdio.h>
#include <float.h>

int main() {
  printf("int 存储大小 : %lu \n", sizeof(int));
  printf("a 存储大小 : %lu \n", sizeof(a));
  printf("float 存储最节数 : %lu \n", sizeof(float));
  printf("float 最小值: %E\n", FLT_MIN);
  printf("float 最大值: %E\n", FLT_MAX);
  printf("精度值: %d\n", FLT_DIG);

    return 0;
}

编码

  • 原码:最高位0代表正,1代表负
  • 反码:如果是正数,反码和原码相同。如果是负数,符号位为1,其它各位与原码相反
  • 补码:正数与原码相同。负数:反码加一
    • 好处:相加不用再考虑正负
    • 负数补码转原码:符号位不动,其它求反,得到的数加一
    • 内存中以补码的形式存储

进制

int a;
// 以 n 进制赋值
a = 10; // 默认十进制
a = 010; //0开头为8进制
a = 0b101; // 0b 开头为 2 进制
a = 0x2f;//    0x开头为16进制

// 以 n 进制显示
printf("十进制 = %d,八进制 = %o, "
       "十六进制小写 = %x , 16进制大写 = %X, "
       "十进制无符号 %u", a, a, a, a, a);


char c = 0x41;
printf("size = %lu, value= %c", sizeof(c), c);

小数

float f = 123.4f;
double d1 = 100.1; // C语言默认都是 double


char ch = 'A';
printf("字符:%c,对应的ascii:%d\n", ch, ch);

类型转换

int x = 1;
double y;
y = (double) x;

字符串

// char 的 \b 是退格符号,例如:
printf("hello word");
char b='\b';
printf("%c%c",b,b);
// 输出 hello wo


// 字符串 s/S char/wchar
printf("%s\n", "hello");


// p 16进制指针
int a = 1;
printf("%p\n", &a);

// 输出一个百分号
printf("%%", );

// printf 还有对其之类的操作,就不多写了

常量:不可被修改

// 宏常量,规范:用大写
#define MAX 100;

// const 常量
const int a = 0;
// c用宏常量多,C++用const多

"字符串常量"
20 // 整数常量

复杂类型

struct // 结构体
union // 共用体
enum // 枚举

typedef // 声明类型别名
sizeof // 得到类型大小,32位系统返回 unsigned int,64位系统返回 unsigned long
#include <stdio.h>

enum month {
    JAN = 1, FEB = 2, MAR = 3, APR = 4, MAY = 5, JUN = 6,
    JUL = 7, AUG = 8, SEP = 9, OCT = 10, NOV = 11, DEC = 12
};

int main() {
    enum month lastmonth, thismonth, nextmonth;
    lastmonth = APR;
    thismonth = MAY;
    nextmonth = JUN;
    printf("%d %d %d \n", lastmonth, thismonth, nextmonth);
    return 0;
}

小知识:

  • 如果不定义值,第0个默认为0,后面的是前面的加1
    enum month {
      JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC
    };
    

运算符

+-*/ 加减乘除
% 取模
i++ , i--  自加/自减

=,+=,-=,*=,/=,%= 赋值

int x, y, z;
x = y = z = 5; //为三个变量同时赋值
// 原理是 (z = 5) 除了赋值外还会返回5
// 所以 if(z=5) 不会报错,如果本意是 if(z == 5),会有问题

逻辑运算符

C语言没有bool类型,使用 0 代表假,非 0 代表真

// 比较运算符,返回0或1
==!=<><=>=

// 与或非
&&||!

// 三目运算符
max = x > y ? x : y;

小知识:

  • 逻辑运算符、三目运算符 不会做额外运算(短路求值的特性),例如:3>1 || x/0 不会报错,因为第二部分就不会运算

位运算符

& 按位与
| 按位或
~ 按位反
^ 按异或

<< 移位
>> 移位

变量作用域

auto
static
register
extern
const
volatile

局部变量

  • auto
  • register
  • static

volatile,不让编译器自动做优化,例如:

int a =1;
a = a + 1;
a = a + 2;
// 以上代码会被编译器自动优化为: a = a+3

volatile int a = 1
// 编译器不会再自动做优化,

auto

  • 代码块内部的变量,默认为 auto,即使不加
  • 变量作用域为一个花括号
int a = 0; //写在代码块外,等价于 extern int a = 0;
int main(int argc, char **args) {
    int a = 1; // 写在代码块里,等价于 auto int a = 1;
    {
        int a = 2; //
        printf("%d\n", a); // 打印2
    }
    printf("%d\n", a); //打印1
}

register

register:建议把变量放入寄存器。

  • 建议指令,如果寄存器不够用,register 可能不生效
register int a = 0; // 这个变量直接放在寄存器中,而不是内存中
a = a + 1;
// 对应汇编
move eax, 0
add eax, 1

// 普通的:
int a = 0;
a = a + 1;
// 对应汇编
move b, 0
move eax, b
add eax, 1
move b, eax

static

static 静态变量

  • 内存位置在程序执行期间不会变
  • 只能被同一个代码块(如果写在代码块里)或同一文件(如果写在文件里)访问
  • 程序加载后就在内存中出现,程序结束后才消失。整个程序运行中 只初始化一次
  • 对于代码块外的变量,如果不加 static,就是 extern。加了 static,就不是 extern 了,就只能在本文件中运行。
  • 对于函数来说,如果不加 static,默认是全局的。如果加了 static,就在单个文件中生效
int my_fun() {
    static int a = 0; // 整个程序中,只执行一次
    a++; // 程序运行中,一直出现在内存中
    printf("%d\n", a);
    return 0;
}


int main() {
    my_fun();
    my_fun();
    my_fun();
}
// 打印结果:
// 1
// 2
// 3

extern:全局变量

全局变量 extern :可以“跨文件”使用

// other.c
int a = 5;

// main.c

#include<stdio.h>
extern int a;

int my_fun() {
    a++;
    printf("%d\n", a);
    return 0;
}


int main() {
    my_fun();
    my_fun();
    my_fun();
    a++;

    printf("%d\n", a);

}


// gcc -o main main.c other.c
// ./main
// 打印结果:
// 6
// 7
// 8
// 9

关于函数

  • 默认是 extern(也可以省略),表示外部函数
  • 加 static 表示 内部(静态)函数
// other.c
#include<stdio.h>

int a = 5; // 在这里定义这个变量

int my_fun2(){
    a++;
    printf("my_fun2 : %d\n", a);
    return 0;
}


// main.c
#include<stdio.h>

extern int a; //这里声明一个全局变量
extern int my_fun2(); // 这里声明,其它地方定义
int my_fun3(); // 不加 extern,也默认为extern


int my_fun() {
    a++;
    printf("%d\n", a);
    return 0;
}


int main() {
    my_fun2();
    my_fun();
    my_fun();
    a++;

    printf("%d\n", a);

}

流程相关

continue
break
goto

选择语句

if语句

// 第一种
if (/* condition */) {
  /* code */
} else if (/* condition */) {
  /* code */
} else {
  /* code */
}
// 1. if(z=5) 不会报错,他会执行一个赋值语句;但你的本意应该是 if(z == 5)
// 2. if(a==b); 多一个分号也不会报错,只不过会直接结束if,后面的代码不在 if 范围中了。

// 第二种
if (/* condition */) {
  /* code */
}



// 第三种
if (/* condition */) {
  /* code */
} else {
  /* code */
}

// 第四种
if (/* condition */) {
  /* code */
} else if (/* condition */) {
  /* code */
} else {
  /* code */
}

switch

int a;
printf("input a=");
scanf("%d", &a);

switch (a) {
    case 0:
        printf("input 0");
        break;
    case 1:
        printf(" and 1");
        break;
    case 2:
        printf("other");
        break;
    default:
        printf("not known!");
}
// 1. break 必须带,不然的话,匹配成功一个,下面的每个 case 都会执行(不知道为啥要这样设计)

?:语法

condition ? exp1 : exp2

循环结构

for (size_t i = 0; i < count; i++) {
  /* code */
}


do {
  /* code */
} while(/* condition */);


while (/* condition */) {
  /* code */
}

goto:不要用

goto label1;
printf("hello");
label1:
printf("world");

关键词:

continue; // 直接执行下一个循环
break; // 跳出循环

数组

int a[10] // 定义了一个数组,其长度为10
// 1. 数组的元素类型必须都一样
// 2. 数组名本身是数组第一个元素的地址对应的常量
printf("%p == %p", a, &a[0]); // 两个都是在内存中的开头位置


// 初始化
int a[3] = {1, 2, 3};
int a[3] = {1, 2}; // 没定义的,默认为0
int a[] = {1, 2, 3}; // 如果不指定长度,自动计算和指定长度

// 3. 语法上,index 可以溢出,例如
a[100] = 999;
a[101];

// 4. 如何得到数组的长度? sizeof(a)/sizeof(a[0])
// 所以这样遍历数组
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
    printf("%d\n", a[i]);
}

二维数组

int a[2][5]; // 定义了一个二维数组,a[0],a[1] 是两个1维数组名
printf("%p = %p = %p", a, a[0], &a[0][0]); // 同一维数组,存放的也是内存地址

// 初始化
int a[2][5] = { {1, 2, 3, 4, 5},
               {3, 4, 5, 6, 7} };

// 省略的自动补0
int a[2][5] = { {1, 2, 3, 4, 5},
              {3} };

// 可以省略第一个大小
int a[][5] = { {1, 2, 3, 4, 5},
               {3} };

多维数组

int a[2][3][5]; // 定义了一个三维数组,其 a[0], a[1] 分别代表2个二维数组

字符相关

字符数组

新建

// 新建1
char a[2];
a[0] = 'a';
a[1] = '\0'; // 它的 ascii 码就是 0,所以也可以 a[1] = 0;

// 新建2
char a1[10] = "abc"; // 这其实是初始化一个字符数组。不能用赋值 a="abc" 是错的
char a2[] = "abc"; // 这个例子实际上长度为 4,因为会多一位 \0

// 新建3:只能只读
char *s = "hello world"; // 这个是先定义一个 字符串常量,然后用指针指向它的首地址。所以是只读。

字符数组和字符串的区别:

  • 字符串是一种特殊的 char [],它必须以 0 结尾
  • char[] 如果中间某个值为0,字符串就到此截断,但整体仍然是个数组
    char a[] = "hello, world";
    a[3] = 0;
    printf("%s", a); // 打印 hel
    
  • 如果一个 char[] 没有以0结尾,我们不把它叫做字符串,printf会出乱码

字符串数组和指针

char a[100] = "hello world";
char *s = "hello world"; // 这个是先定义一个 字符串常量,然后用指针指向它的首地址。所以是只读。

例题:如何合并两个字符串?

char a[100] = {0};
char b[100] = {0};
char c[200] = {0};

scanf("%s", a); // 这里不是 &a
scanf("%s", b); // 如果用户的输出超过100个,会有溢出

int idx1, idx2;
idx1 = idx2 = 0;
while (a[idx1]) {
    c[idx1] = a[idx1];
    idx1++;
}
while (b[idx2]) {
    c[idx1] = b[idx2];
    idx1++;
    idx2++;
}

printf("%s", c);

字符串scan和print

printf

  • %s 输出一个字符串
  • %c 输出一个字符
  • %d 以十进制输出一个有符号整型
  • %u 以十进制输出一个无符号整型
  • %o 以八进制输出一个整数
  • %x 以十六进制输出一个小写整数
  • %X 以十六进制输出一个大写整数
  • %f 以十进制输出一个浮点数
  • %e 以科学计数法输出一个小写浮点数
  • %E 以科学计数法输出一个大写浮点数

scanf 有一些问题:

  1. 认为空格和回车,以及其它终止符都是结束,终止符号:
    • 0x20 空格
    • \t 水平制表符(tab键)
    • \n 换行
    • \v 垂直制表符
    • \f 换页
    • \r 回车
  2. 如果 char [10] a,然后 scanf 输入长度为3的字符串,会添加0之后赋值给前几个元素,后面的元素保持不变。
  3. 如果用户输入超过 a 的长度,会溢出
putchar('a') \\ 一次输出一个字符
printf("hello") \\ 不多说

getchar() \\ 返回 int,是你输入的字符对应的 ascii 

// scanf:
int a;
scanf("%d", &a); // 第二个变量是变量的地址
printf("%d\n",a);
  • gets 和 puts 底层是由 putchar/getchar 实现的

char a[10];
gets(a);
// 1. 用户输出超长后会报错
// 2. 空格不代表结束,回车代表结束
// 3. 仍然有溢出危险

fgets(a, sizeof(a), stdin);
// 1. 用户超长后不报错,而是截断
// 2. 回车和空格都会放进去
// 3. 没有溢出危险



puts(a); // 跟 printf 功能差不多,自动加一个 \n,所以和 gets 连用会出现两次 \n
fputs(a, stdout);
// 1. 不会自作主张加 \n

string.h

  • 还有一类print/scan
// 字符串格式化:sprintf
char a[100];
sprintf(a, "%d: Hello, %s, welcome.", 1, "Tom");
// 把格式化后的字符串,放入a
// 可以这么理解:printf 是输出到标准输出设备,sprintf 是输出到字符数组
// 额外一个小应用:int 转 char[]

// 从指定格式中的字符中提取:sscanf
char a[] = "12+21";
int i, j;
sscanf(a, "%d+%d", &i, &j);
// 额外一个小应用:char[] 转 int(其实也能转 double)
// 也可以 char c;sscanf(a, "%d%c%d", &i, &c, &j);

// 字符串中提取数字
char a[100] = "105hellohello";
char *stopstring;
long int i = strtol(a, &stopstring, 6);
// 1. 以 6 进制提取
// 2. stopstring 是提取后的剩余部分
// 3. 类似的有这些:strtof, strtol, strtold, strtold, strtoll 等
// 4. 类似的还有 atoi, atof 等
// 5. 没有反过来转化的内置函数,可以用 sprintf 实现
场景输出输入备注
字符,std_ioputchar('a')int a = getchar();
字符,文件char c=getc(p);putc(text[i], p);c==EOF为终止条件
同上fgetc(p)fputc(p)
字符串,std_iochar a[10];
     gets(a);
puts(a)有溢出风险,回车也有问题,不要用
字符串,std_iochar a[10];
fgets(a, sizeof(a), stdin);
fputs(a, stdout);建议使用
fgets会提取末尾的换行符,fputs不会自动添加换行符
字符串,文件char buf[1024] = {};
     fgets(buf, sizeof(buf), p); // 这里一次读取一行
fputs(a, p);feof(p)=1 判断到了结尾
字符串格式化,std_ioscanf("%d", a);
不会把换行符号加入到a中
   
   printf("%d\n",a);
不要提取%s,会有问题
字符串格式化,char arraychar a[] =   "12+21";
     int i, j;
     sscanf(a, "%d+%d", &i, &j);
sprintf(a, "%d: Hello, %s, welcome.", 1, "Tom");同上的问题
字符串格式化,文件fscanf(p1, "%d%c%d=\n", &a, &b, &c);fprintf(p2, "%d%c%d=%d\n", a, b, c, a + c);同上的问题
feof(p)=1 判断到了结尾

常用字符串方法 string.h

#include<string.h>

字符串长度:strlen

unsigned long len = strlen(a);
// 1. 这个长度不包括末尾的 0
// 2. 1个汉字算3个
// 3. 到第一个0为止,既是数组后面还有内容

字符串合并:strcat,strncat

strcat(a, b); // 把 a 和 b 合并,并且放到 a
// a 必须有足够的空间,否则报错
strncat(a, b, 3); // 最多追加 b 的前 3 个字符

字符串拷贝:strcpy,strncpy

strcpy(a, b); // 把 b copy 到 a 中
// 如果超过 a 的大小,不会报错,而是会继续写入后续的内存(或许会导致错误)
strncpy(a, b, sizeof(a) - 1);
// 最多只复制 sizeof(a) - 1 个,可以防止溢出

字符串比较:strcmp,strncmp

int is_unequal = strcmp(a, b);
// 如果不同,返回非0。如果相同,返回0
// \0 之后的元素不参与比较
int is_unequal = strncmp(a, b, 2);
// 比较前2个字符串
// a == b 是不对的,因为这个比较的是内存

字符串查找:strchr,strstr

char a[] = "hello world!";
char *s;
s = strchr(a, 'w');
// 1. 返回一个指针,指向第一次出现 'w' 的位置
// 2. 因此 printf("%s", s); 输出 world!,也就是遇到 \0 才结束
// 3. 如果找不到,返回 null,用 s==NULL 做判断

s = strrchr(a, 'w')
// 返回一个指针,指向最后一次出现 'w' 的位置

s = strpbrk(a, "wld");
// 返回一个指针,指向第一次出现一个 char 集合的位置


s = strstr(a, "wor"); // 功能相似,入参是字符串
// 如果第二个为空


size_t	 strcspn(const char *__s, const char *__charset);
size_t	 strspn(const char *__s, const char *__charset);

字符串分割:strtok

char a[100] = "abc_efg_123_666666";
char *s;

s = strtok(a, "_"); // 返回 abc
printf("%s\n", s);

s = strtok(NULL, "_");// 继续查找,返回 efg。如果查完了,返回null
printf("%s\n", s);

// 使用示例:
char a[100] = "abc_efg_123_666666";
char *s;

s = strtok(a, "_");
while (s) {
    printf("%s\n", s);
    s = strtok(NULL, "_");
}

char的一些有关技巧

// 小写转大写的技巧
char a = 'a';
if (a >= 'a' && a <= 'z') { // 1. char 可以当成 int 用
    a -= ' '; // 2. 大小写的 ascii 差 32,空格的 ascii 码也是 32(ascii设计巧妙)
}

// 同样原理,可以字符转数字
int a = '1';
if (a >= '0' && a <= '9') {
    a -= '0';
}

内存操作(与strn开头的函数区别是,它们遇到 0 不会停止):

  • memcpy
  • memmove,功能同 memcpy,它是针对内存有重叠的情况,会先放到另一个临时位置
  • memcmp,逐字节比较
  • memchr,找到 chr 第一次出现的位置,并返回指针
  • memset

memset:内存格式化

int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
memset(a, 0, sizeof(a));
// 入参:置空的区域的首地址,0,这块内存大小(单位 Byte)
// 第二个参数范围 0-255,以 char 为单位(“2个16进制”,“8个二进制”,“一个字节”)格式化。与 a 的类型无关

memcpy 和 strcpy: 内存拷贝

// memcpy: 内存拷贝。要确保没有内存重叠区域
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int b[10] = {0};
memcpy(b, a, sizeof(a));
// 入参:目标地址,源地址,拷贝的大小(单位 Byte)

// strcpy:也是内存拷贝,到 s2 的0为止
char *s1 = malloc(10);
char *s2 = "hello";
strcpy(s1,s2);
//

ctype.h

有很多类似 islower, isupper, isdigit 之类的函数

tolower, toupper

可以用类似 c<='9' && c>='0',但在 ebcdic 机器上会失效

字符串常量

"xyz"+1 // 指向y,printf("%s\n","xyz"+1); 结果是 yz

*"xyz" // 指向第 0 个字符 x,  printf("%c\n", *"xyz"); 结果是 x

"xyz"[2] // 指向 z

*("xyz" + 2) // 指向 z 本身,printf("%c\n", *("xyz" + 2));

使用例子(打印n个星号):

int n = 3;
printf("%s\n", "**********" + (10 - n));

函数

  1. 使用前必须先定义或声明。可以先声明,然后在底下定义。
  2. 任何位置执行 exit(0) 会让整个程序中止
  3. 字符数组作入参是这样的 int my_func(char *a)

多文件编译

// my_utils.c:
int my_add(int a, int b) {
    return a + b;
}

// main.c:
#include<stdio.h>

int my_add(int a, int b);

int main() {
    int a = 1;
    int b = 2;
    int c = my_add(a, b);
    printf("%d\n", c);
    return 0;
}

// 编译:
// gcc -o my_package main.c my_utils.c
// 执行:
// ./my_package

往往这么做

// my_utils.h
int my_add(int a, int b) {
    return a + b;
}

// main.c
#include<stdio.h>
#include "my_utils.h"

int main() {
    int a = 1;
    int b = 2;
    int c = my_add(a, b);
    printf("%d\n", c);
    return 0;
}

// gcc -o my_package main.c
// ./my_package

另外,好像还有个规范:.h 文件只声明函数,函数定义放到 my_utils.c

预定义符号

printf("编译的源文件名:%s\n", __FILE__);
printf("编译源文件名:%s\n", __FILE_NAME__);
printf("当前行号:%d\n", __LINE__);
printf("编译日期:%s, 编译时间 %s", __DATE__, __TIME__);

基本语法

// 移除一个宏定义。如果一个现存的名字需要重定义,那么也要先用 #undef 移除旧定义
#undef name



// if语句
#if constant
     statements
#endif

// 例如:
#define DEBUG 1
#define VERBOSE 1
#if DEBUG
    printf("debug\n");
#elif VERBOSE
    printf("verbose\n");
#else
    printf("default\n");
#endif
#define TEST// 定义了一个宏

#ifdef TEST // 如果定义了 TEST,就编译这里面的,否则不编译
    printf("dev");
#endif

#ifndef TEST
    printf("prod") // 如果没定义就不编译,定义了就编译
#endif

宏的应用:如果一个头文件被10个源文件 include,那么它会被编译10次,用下面的方法防止重复声明(类似单例模式)

#ifndef _MY_UTILS
#define _MY_UTILS

int my_add(int a,int b){
return a + b;
}

#endif

还有一个规范:.h 文件一般只放函数的声明,.c 文件放函数的定义

一个错误的使用:

#include <stdio.h>

int func1();

int main() {
    func1(); // 这里编译器不会提示错误,但会产生一个随机的值
    return 0;
}

int func1(int a){
    printf("%d",a);
    return 0;
}

对于真的无入参函数,规范:使用 int func1(void);,C++ 没有这个问题。

define

// 每当有符号 name,都替换为 stuff
#define name stuff

// 进入死循环
#define do_forever for(;;)

// 可以作为switch 的辅助
#define CASE break;case


#define SQUARE(x) (x)*(x)
printf("%d\n", SQUARE(3+1));
// 注意,这样容易错: #define SQUARE(x) x*x


// 下面是个有趣的宏
#define repeat do
#define until(x) while(!(x))

int main(int argc, char **args) {

    int i = 0;
    repeat {
        i++;
    } until(i >= 10);

    return 0;
}

说明

  • 尽量避免滥用宏,别人难以理解
  • 宏与函数比较
    • 函数传参有性能损失,对于高频代码块,用宏无性能损失
    • 函数需要检查入参的类型,宏不用

标准库 stdlib.h

算术


int abs(int value);
long labs(long __a);


div_t a = div(10, 3);
printf("商 = %d, 余数 = %d", a.quot, a.rem);

// ldiv

随机数

#include<time.h>
#include<stdlib.h>

srand(100); // 指定种子。 如果不定义种子,不会随机定种子
int a = rand(); // 值范围是 0-RAND_MAX

// 如何得到真随机:
int t=(int) time(NULL);
srand(t);
int b = rand();

// 例子:如何得到0-100的随机数?
rand() % 101;

// 例子:shuffle
void shuffle(int *arr, int len_arr) {
//    只在第一次调用时初始化,节省资源
    static int no_seed = 1;
    if (no_seed) {
        no_seed = 0;
        srand((int) time(NULL));
    }

    for (int i = len_arr - 1; i > 0; i--) {
        int where;
        int tmp;
        where = rand() % i;
        tmp = arr[where];
        arr[where] = arr[i];
        arr[i] = tmp;
    }
}

字符串转数字

int a = atoi("-123");
// atol 转 long
// atoll 转 long long
// atof 转 double


// strtol 和 strtoul
// 功能1:同时记录数字到哪为止
// 功能2:支持 n 进制
char *unused;
int a = strtol("   123123aaaa", &unused, 10);
printf("%d\n", a);
printf("字符串部分:%s\n", unused);
  • 允许前面有多个空格,会跳过这些空格

system

#include <stdlib.h>

int main()
{
    system("ls -l"); // 运行成功返回0
    return 0;
}

system 返回什么?

  • 被调用者的返回
  • 如果调用 linux 命令,成功返回0
  • 如果调用其它编译好的 another.c,返回 another.c#main 函数的 return (???并不是)

math.h

double x = sqrt(5.5);
double x = pow(1.2, 2.3);

exp(x);
log(x); // ln(x)
log2(x);
log10(x);

三角函数类

sin/cos/tan
asin/acos/atan
atan2(x, y) // 相当于 atan(x/y)

sinh/cosh/tanh
asinh/acosh/atanh

对象类

  • frexp:把 x 分解为 $y*(2^n)$
  • ldexp:计算 $y*(2^n)$
  • modf:把浮点数分解为整数部分+小数部分
double x = 1024;

double fraction;
int e;

// 把 x 分解为 y * (2^n)
fraction = frexp(x, &e);
printf("x = %.2lf = %.2lf * 2^%d\n", x, fraction, e);
// x = 1024.00 = 0.50 * 2^11


double x = ldexp(0.65, 3);
// 计算 0.65 * (2^3)

取整类

double x = floor(2.3);
double x = ceil(2.3);

double x = fabs(-2.3); // 浮点数版本的 abs
double x = fmod(7.1, 3.5); // 求余数

时间

clock(): CPU 滴答次数

clock_t start_t, end_t;
double total_t;
int i;

start_t = clock();
printf("开始一个大循环,start_t = %ld\n", start_t);
for (i = 0; i < 10000000; i++);
end_t = clock();
printf("大循环结束,end_t = %ld\n", end_t);

total_t = (double) (end_t - start_t) / CLOCKS_PER_SEC;
printf("处理器时钟滴答次数:%lu\n", end_t - start_t);
printf("CPU 占用的总时间:%f\n", total_t);
printf("程序退出...\n");

结果:

程序启动,start_t = 5545
开始一个大循环,start_t = 5545
大循环结束,end_t = 22768
处理器时钟滴答次数:17223
CPU 占用的总时间:0.017223
程序退出...

time(NULL) 当前秒数

// 方法1:
time_t seconds;
time(&seconds);

// 方法2:
time_t seconds = time(NULL);

printf("自 1970-01-01 起的秒数 = %ld\n", seconds);

timediff,相差多少秒

time_t first, second;
time(&first);
sleep(2); //  单位是秒,需引入 unistd.h

time(&second);
printf("The difference is: %.2f seconds", difftime(second, first));
// The difference is: 2.00 seconds

时间结构体

时间结构体的定义(time.h 中定义的):

struct tm
{
   int tm_sec;         /* 秒,范围从 0 到 61,考虑了润秒      */
   int tm_min;         /* 分,范围从 0 到 59      */
   int tm_hour;        /* 小时,范围从 0 到 23     */
   int tm_mday;        /* 一月中的第几天,范围从 1 到 31    */
   int tm_mon;         /* 月,范围从 0 到 11(注意)  */
   int tm_year;        /* 自 1900 年起的年数      */
   int tm_wday;        /* 一周中的第几天,范围从 0 到 6 */
   int tm_yday;        /* 一年中的第几天,范围从 0 到 365   */
   int tm_isdst;       /* 夏令时               */
};

新建1:手动新建

struct tm t;
t.tm_sec = 10;
t.tm_min = 10;
t.tm_hour = 6;
t.tm_mday = 25;
t.tm_mon = 3 - 1;
t.tm_year = 1989 - 1900;
t.tm_wday = 6;

// 打印 结构体时间
printf("%s\n", asctime(&t));
// Sat Mar 25 06:10:10 1989

新建2: localtime/gmtime。 以秒记时的时间 => 结构体时间

time_t seconds = time(NULL);
printf("%s\n", ctime(&seconds));

struct tm *t;
t = localtime(&seconds);
printf("%s\n", asctime(t));

t = gmtime(&seconds);
printf("%s\n", asctime(t));

mktime: 结构体时间 => 以秒记时的时间

struct tm info;
info.tm_year = 2001 - 1900;
info.tm_mon = 7 - 1;
info.tm_mday = 4;
info.tm_hour = 0;
info.tm_min = 0;
info.tm_sec = 1;

long ret = mktime(&info);
if (ret == -1) {
    printf("错误:不能使用 mktime 转换时间。\n");
} else {
    printf("%s\n", asctime(&info));
    printf("%s\n", ctime(&ret));
}

strftime:结构体时间转字符串

time_t seconds = time(NULL);
struct tm *t = localtime(&seconds);;

char buffer[80];
strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", t);//以年月日_时分秒的形式表示当前时间
printf("%s\n", buffer);

/*format如下:它们是区分大小写的。
    %a 星期几的简写
    %A 星期几的全称
    %b 月分的简写
    %B 月份的全称
    %c 标准的日期的时间串
    %C 年份的后两位数字
    %d 十进制表示的每月的第几天
    %D 月/天/年
    %e 在两字符域中,十进制表示的每月的第几天
    %F 年-月-日
    %g 年份的后两位数字,使用基于周的年
    %G 年分,使用基于周的年
    %h 简写的月份名
    %H 24小时制的小时
    %I 12小时制的小时
    %j 十进制表示的每年的第几天
    %m 十进制表示的月份
    %M 十时制表示的分钟数
    %n 新行符
    %p 本地的AM或PM的等价显示
    %r 12小时的时间
    %R 显示小时和分钟:hh:mm
    %S 十进制的秒数
    %t 水平制表符
    %T 显示时分秒:hh:mm:ss
    %u 每周的第几天,星期一为第一天 (值从0到6,星期一为0)
    %U 第年的第几周,把星期日做为第一天(值从0到53)
    %V 每年的第几周,使用基于周的年
    %w 十进制表示的星期几(值从0到6,星期天为0)
    %W 每年的第几周,把星期一做为第一天(值从0到53)
    %x 标准的日期串
    %X 标准的时间串
    %y 不带世纪的十进制年份(值从0到99)
    %Y 带世纪部分的十进制年份
    %z,%Z 时区名称,如果不能得到时区名称则返回空字符。
    %% 百分号
*/  

报错相关

// signal.h
raise(4);

// stdlib.h
void abort(void);
void ateixt(void (func)(void)); // 把一个函数注册为退出函数,程序退出时调用它
void exit(int status);

// assert.h
assert(0 == 1);  // 会打印异常并退出

// 在 #include <assert.h> 之前插入下面这一句,可以使全部 assert 不生效
#define NDEBUG

qsort

void qsort (
    void* base, //要排序的目标数组
    size_t num,     //待排序的元素个数
    size_t width,    //一个元素的大小,单位是字节
    int(*cmp)(const void* e1, const void* e2) // 函数指针,定义如何比较两个元素

);        

用法举例:

int cmp_int(const void *e1, const void *e2) {
    return *(int *) e1 - *(int *) e2;
}

int base[] = {1, 5, 3, 7, 4, 7, 1};
qsort(base, sizeof(base) / sizeof(int), sizeof(int), cmp_int);
for (int i = 0; i < sizeof(base) / sizeof(int); i++) {
    printf("%d,", base[i]);
}

不同类型的 cmp

// 如果你要比较的数据是浮点型:
int cmp_float(const void* e1, const void* e2)
{
	return (int)(*(float*)e1 - *(float*)e2);
}


// 如果你要比较的是字符串的大小:
int cmp_str_size(const void* e1, const void* e2)
{
	return strcmp((char*)e1,(char*)e2);
}


// 如果你要比较的是字符串的长度:
int cmp_str_len(const void* e1, const void* e2)
{
	return strlen((char*)e1)-strlen((char*)e2);
}


// 如果你要比较的数据是结构体变量:
int cmp_by_age(const void*e1, const void*e2){
	return (int)(((stu*)e1)->weight - ((stu*)e2)->weight);
}

您的支持将鼓励我继续创作!