编译与宏
#
开头为预处理命令,包括 #include
引入文件 #define
宏,在预处理阶段处理
函数声明与语法检测在编译期检查,函数定义在链接时检测
__attribute__(constructor))
有该宏上标的函数先于 main
执行
#include <stdbool.h>
使得 c 支持 bool
类型
extern
引用其他文件已定义过的全局变量
__typeof()
宏获得变量类型,常用于定义宏
#define swap(a, b) {\
__typeof(a) __temp = a;\
a = b; b = __temp;\
}
// 或者 a ^= b; b ^= a; a ^= b;
// 根据变量类型,获得指定输出
#define Type(a) _Generic((a), \
int : "%d",\
double : "%lf",\
float : "%f",\
long long : "%ld",\
const char * : "%s",\
char * : "%s"\
)
EOF
宏常量为 -1, NULL
为 0
变参函数引入 stdarg.h
#include <stdarg.h>
int max(int num, ...) {
va_list arg; // 声明参数
va_start(arg, num); // 使得 arg 指向起始参数
while (num--) { // num 帮助判断参数个数
int temp = va_arg(arg, int); // 开始获得改类型的参数
}
va_end(arg); // 释放 va_list
}
// 可通过 myprintf(const char *frm, ...) 自己实现打印函数
// 防止多次引入头文件宏重复声明
#ifndef _LOG_H
#define _LOG_H
// 通过头文件编译打印技巧
#ifdef DEBUG
#define log(frm, args...) { \
printf("[%s : %s : %d]", __FILE__, __func__, __LINE__); \
printf(frm, ##args); \
printf("\n"); \
}
#else
#define log(frm, args...)
#endif
#define contact(a, b) a##b // 拼接合并 一个#变成字符常量
#endif
工程开发规范,.h
头文件放函数声明,.c
文件放函数定义,头文件和源文件分离,可以解决函数定义引用函数找不到的问题
工程项目目录编排:
src
源码目录lib
外部库目录 可用静态库文件lib*.a
include
头文件.h
bin
可执行文件build
构建过程中间文件(可选)
makefile
文件
.PHONY: clean // 虚拟 clean 文件,不检查
all: lib/lib1.a test.o // 依赖
gcc test.o -L./lib -llib1 -DDEBUG // 指定库 开启宏声明
test.o: test.cpp
gcc -I./include -c test.cpp // -c 不链接
clean:
rm -rf a.out test.o // 清理
语言特性与库
c 语言中的 switch
只能判断整型值变量
math.h
中返回和传参都是高精度
需要了解的一个最大公约数函数
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
printf
scanf
返回值为读入长度
对于 scanf
函数,我们可以通过 scanf("%[^\n]s", str)
来跳过换行输入等
sprintf
以指定格式打印到字符串,sscanf
从字符串中读出类型变量
c 语言中自带 stdin
stdout
stderr
文件符,对文件进行操作
FILE *fout = fopen("output", "w");
fprintf(stdout, "stdout = %s\n", str);
fprintf(fout, "fout = %s\n", str); // 写入文件
fclose(fout); // 注意关闭文件
使用随机数,引入 time.h
#include <time.h>
srand(time(0));
int val = rand() % 100; // 0 - 100 的随机数
使用动态内存函数,引入 stdlib.h
函数指针也是参数,可进行传参调用指定方法函数
double newton(double (*F)(double, double), double(*f)(double), double n);
// main 传入参数个数,参数数组,环境变量
int argc, char *argv[], char **env
指针、堆栈空间、结构体
const int *x
指针常量 int * const x
常量指针
函数内声明的变量在栈空间中,所以需要 malloc
指针等帮助下在堆空间创建,传出去,栈空间内的变量会被销毁
结构体成员描述关系会影响结构体构造的函数大小,比如 char
可能跟内存对齐 4 个字节 或 8 字节 (看系统)
自定义宏,查看结构体成员偏移
#define offset(T, a) (long) (&(((T *) (NULL))->a))
64 位系统指针变量大小是 8 字节,32 位系统指针变量大小为 4 字节
指针根据声明类型去跳位,p++
char
类型跳 8 位,int
类型跳 32 位
float
占 4 字节,double
占 8 字节,long long
占 8 字节
对于函数指针,我们可以通过 typedef
函数提升至类型,方便调用
内存泄漏,声明一块内存后,对其访问丢失,没有释放
函数系统栈空间不超过 8MB,函数结束时释放
在代码中出现的所有同样的字符串,都是字符串常量,指向同一静态区
对于常量,全局变量,静态变量(即使在函数内)都存在静态区,程序结束释放
static
在 c 语言的作用主要是隐藏,不让在文件外导出;唯一一次初始化(在程序启动时,函数中不会初始化它),保持变量的持久;默认设置为 0
申请内存函数申请的都是虚拟内存,在虚地址上
malloc
申请后,在实际访问情况下时若内存不够才尝试缺页异常;不会对原内存内容进行修改
calloc
在申请内存后,立刻把原内存中的内容置 0 了,进行了访问
realloc
在当前指针上,基于原内存地址扩充
共用体中所有元素公用一个空间,一般机器都是大端模式
union IP {
struct {
unsigned char a1;
unsigned char a2;
unsigned char a3;
unsigned char a4;
} ip;
unsigned int num;
};