基本数据结构
一个二进制位是一个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 有一些问题:
- 认为空格和回车,以及其它终止符都是结束,终止符号:
0x20
空格\t
水平制表符(tab键)\n
换行\v
垂直制表符\f
换页\r
回车
- 如果
char [10] a
,然后 scanf 输入长度为3的字符串,会添加0之后赋值给前几个元素,后面的元素保持不变。 - 如果用户输入超过 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_io | putchar('a') | int a = getchar(); | |
字符,文件 | char c=getc(p); | putc(text[i], p); | c==EOF为终止条件 |
同上 | fgetc(p) | fputc(p) | |
字符串,std_io | char a[10]; gets(a); | puts(a) | 有溢出风险,回车也有问题,不要用 |
字符串,std_io | char 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_io | scanf("%d", a); 不会把换行符号加入到a中 | printf("%d\n",a); | 不要提取%s,会有问题 |
字符串格式化,char array | char 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));
函数
- 使用前必须先定义或声明。可以先声明,然后在底下定义。
- 任何位置执行
exit(0)
会让整个程序中止 - 字符数组作入参是这样的
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);
}