标准输出函数说明
头文件:stdio.h
原型:int printf(const char *format,…);
format : 格式控制字符串
…: 可变参数列表
返回值:输出字符的数量
printf("%d\n",printf("%d",n));//输出一个n和n字符的数量
标准输入函数说明
头文件:stdio.h
原型:int printf (const char *format,…);
format : 格式控制字符串
…:可变参数列表
返回值:成功读入的参数个数
&n:传出参数
合法性:返回值 >= 0
scanf("hello");//返回值为0
(根据非法)循环读入
while(scanf() != EOF);//EOF为end of file,在这里为-1
取反循环读入
while(~scanf("%d", &n));
带空格输入字符串:
scanf("%[^\n]s", str);//[^\n]为除了'\n',整句为除了换行都输入
弊端:没有处理掉‘\n’,在输入完毕按回车时输入一个换行,换行一直存在,会导致一个死循环,就是一直换行输入换行之前输入的字符串,但是只有换行之前输入是有效的,scanf返回值为换行之前成功输入的参数个数
处理:吐掉换行
getchar();
这个函数可以吐掉上一个字符
sprintf 、fprintf
sprintf 、 sscanf -> string -> 字符串拼接【输出到字符串中去、从字符串中输入】
fprintf 、fscanf -> file -> 中文件操作【输出到文件中去、从文件中输入】
#include<stdio.h>
int main(){
int n;
char str[100] = {0};
int arr[4] = {0};
//scanf("%d", &n);
//printf("%d\n", n);
sprintf(str, "%d.%d.%d.%d", 192, 168, 0, 1);//拼接字符串到str
printf("str = %s\n", str);//打印字符串str
sscanf(str, "%d.%d.%d.%d", &arr[0], &arr[1], &arr[2], &arr[3]);//从str字符串中输入
for(int i = 0; i< 4; i++){
printf("%d\n", arr[i]);//标准输出
}
FILE *fp = fopen("./output","a+");//打开文件output,“a+”为追加性写入
fprintf(fp, "str = %s\n", str);//从fp指向的文件output中输出str
fclose(fp);//关闭fp指向的文件output
char ans[100] = {0}, temp;
int offset = 0;
FILE *fin = fopen("./output", "r");//打开文件output,“r”为可读性
while(fscanf(fin, "%c", &temp) != EOF){//从fin指向的文件中输入到temp中
offset += sprintf(ans + offset, "%c",temp);//一个temp拼接到ans + offset,offset为中间变量,比如ans + 1为数组位置,最终保存文件全部内容到ans中,offset += sprintf()返回值,每次加一
}
fclose(fin);//关闭fin指向的文件
printf("%s\n", ans);//标准输出ans
return 0;
}
位运算(二进制)
位权:比如二进制的位权就是pow(2,n-1),十进制的位权就是pow(10,n-1),n为位数
& :有0则0
判断奇偶:
n & 1 == 1 为奇数
| :有1则1
^ :(逆运算)同为0,异为1
【必须是整型值】
作用:
1、交换
aba=b
a ^=b;
b ^=a;
a ^=b;
a’ = a ^ b;
b’ = b ^ a’ = b ^ a ^ b = a;
a’’ = a’ ^ b’ = a ^ b ^ a = b;
~ :取反
作用:
1、循环读入
while(~scanf("%d", &n));
-1
原码: 0000 0000 0000 0000 0000 0000 0000 0001
取反:1111 1111 1111 1111 1111 1111 1111 1110
加1: 1111 1111 111 1111 1111 1111 1111 1111
~(-1):0000 0000 0000 0000 0000 0000 0000 0000
(~scanf(“%d”, &n)):在scanf返回值前取反,当成功读入参数个数大于或等于0时,即为真值,就可进行循环;当成功读入参数为-1时,即为0时,即为假值时,结束读入循环。
2、竖式计算
-2= -1 - 1
1111 1111 111 1111 1111 1111 1111 1111
— 0000 0000 0000 0000 0000 0000 0000 0001
1111 1111 111 1111 1111 1111 1111 1110
<<,>>:左右移,左移乘(*)2倍,空位补0。右移除(/)2倍,空位补符号位
数学函数库
pow 函数 :指数函数
头文件:math.h
原型:double pow(double a,double b);
a:底数
b:指数
返回值:返回a的b次方
sqrt 函数 :开平方函数
头文件:math.h
原型:double sqrt(double x);
x :被开方数
返回值:返回根号x
ceil 函数 :上取整函数
头文件:math.h
原型:double ceil(double x);
x :某个实数
返回值:返回[x] 例子: ceil(4.1) = 5
floor 函数 :下取整函数
头文件:math.h
原型:double floor(double x);
x :某个实数
返回值:返回[x] 例子:floor(4.9) = 4
/*abs 函数 :整数绝对值函数
头文件:stdlib.h
原型:int abs(int x);
x :某个整数
返回值:返回|x| 例子:abs(-4) = 4 */
fabs 函数 :实数绝对值函数
头文件:math.h
原型:double fabs(double x);
x :某个实数
返回值:返回|x| 例子:abs(-4.5) = 4.5
log 函数 :以e为底对数函数
头文件:math.h
原型:double log(double x);
x :某个实数
返回值:返回logeX 例子:log(9) = 2.1972…
log10 函数 :以10为底对数函数
头文件:math.h
原型:double log10(double x);
x :某个实数
返回值:返回log10X 例子:log10(100) = 3
注意:其他底数可以用换底公式来转换
acos 函数 :acos函数
头文件:math.h
原型:double scos(double x);
x :角度的弧度值
返回值:返回arccos(x) 例子:acos(-1) = 3.1415926…
比较运算符
代表假值:0、NULL、‘\0’
‘==’: 如果a == b,则返回值为真值,反则为假值
‘!=’ : 如果a!=b,则返回值为真值,反则为假值
‘<=(=<) 、 =>(>=)’
‘!’:注意 ‘!!(x)’ 为逻辑归一化,把真值归为1,把假值归为0.如!(3) = 0,!!(3) = 1;
分支结构
一条语句:空语句、单语句(分号结束前面的单行语句) 、复合语句(花括号{}中间的语句)
if 语句
if成立 作用一条语句
switch 语句
CPU的分支预测
一个指令整个操作(每一步需要一个时钟周期):
F:取指
D1:指令与解析
D2:数据与解析
EX:执行
WB:返回
时钟周期:上面每一个部分为一个时钟周期
串行执行:执行完一整个操作(整个操作包括一系列指令)再进行下一个操作,也就是一个指令执行完得等整个操作执行完才再开始执行。比如执行一个指令要五个时钟周期,执行五个指令要二十五个时钟周期
并行执行:如下图,五个指令执行完需要九个时钟周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FDzgfdP3-1678493644813)(C:\Users\86166\AppData\Roaming\Typora\typora-user-images\image-20210529195914588.png)]
CPU就是并行执行if语句,也就是if和else同时执行,但是只能执行if或者else,所以CPU有个分支预测,根据以前的习惯来预测执行,所以就存在分支预测失败,如果有过多的if语句分支预测失败可能性提高,但是当预测失败,CPU有套容错的机制,会自我检查的机制,出错时CPU会将中间运行的指令全部推掉,重新再来,并且改为串行执行方式来执行,这样执行的时间就会变得较长。
通过利用宏来提高分支预测的成功率:
Unix中的两条宏:
#define likely(x) //likely 代表x经常成立
#define unlikely(x) //unlikeil 代表x不经常成立
__builtin_expect(!!(x),1)
__builltin_expect(!!(x),0)
这里的 builtin_expect(!!(x), 1) 就是告诉CPU x为1经常成立,提高分支预测成功的可能性,同样builltin_expect(!!(x),0) 就是告诉CPU x为负不经常成立
附录:
__builtin_expect (long exp, long c):用来引导gcc进行条件分支预测
__builtin_ffs(x):返回x最后一个为1的位是从后向前的第几位
__builtin_popcount(x):x中1的个数
__builtin_ctz(x):x末尾0的个数。x=0时结果未定义
__builtin_clz(x);x前导0的个数。x=0时结果未定义
__builtin_prefetch (const void *addr,…):对数据手工预取的办法
__builtin_types_compatible_p(type1,type2):判断type1和type2是否相同的数据类型
__builtin_constant_p (exp): 判断exp是否在编译时就可以确定其为常量
__builtin_parity(x):x中1的奇偶性
__builtin_return_address(n):当前函数的底n级调用者的地址
循环结构
while(表达式){},表达式为真则进入循环
do{}while(表达式), 一定循环一次
for(初始化;循环条件;执行后操作){};
逻辑运算符
&&短路原则为遇到假就不再运算后面的表达式
int a = 0, b = 0;
if((a++) && (b++)){}else{}
//到这a为1,b为0;
&&用于空格控制(最后一个元素后面没有空格):
普通的一个情况:
if(i != a[0]) printf(" ");//第一个元素前不加空格
printf("%_",a[i]);//其他元素前输出都带一个空格
利用&&短路原则:
i && printf(" ");//如果i不为0,就输出一个空格,如果为0则不输出
printf("%_",a[i]);
前后置运算
后置加加/减减:a++/a-- ,先返回a的值再加加/减减
前置加加/减减:++a/–a ,先加加/减减再返回a的值
随机数
rand()得到的随机数为伪随机数,即已经规定好的
srand(x) x为一个变量作为随机种子
#include<time.h>
srand(time(0));//time(0)为一个随机种子
函数定义声明
未声明 :错误暴露编译期(是否存在语法层面错误)
未定义:错误暴露链接期
源文件—>预编译(编译期)—>对象文件.o/.obj—>链接(a.out)/.exe(链接期)
编译头文件:g++/gcc -I./ -c xx.cc/xx.cpp
查看对象文件声明:ld xx.o
删除所有对象文件:rm *.o
链接多个对象文件:g++ xx.o xxx.o xxxx.o…
头文件源文件
源文件:xxx.c(c)、xxx.cpp/xxx.cc(c++)
头文件:xxx.h
包含:“ ” ,<>从系统库中
条件式编译:
#ifndef _HEAD3_H//如果没有定义_HEAD3_H
#define _HEAD3_H//就定义_HEAD3_H,_HEAD3_H部分可以自己命名
#endif
条件式编译有效地解决对于 一个(多个不行)源文件中编译链接所产生的重复包含问题。
包含多个头文件到一个头文件
#ifndef _HEAD3_H//如果没有定义_HEAD3_H
#define _HEAD3_H//就定义_HEAD3_H,_HEAD3_H部分可以自己命名
#include<head1.h>
#include<head2.h>
#include<head3.h>
.
.
.
#endif
工程开发规范:
头文件只能放声明,源文件只能放定义,
工程开发规范有效地解决对于 多个源文件中编译链接所产生的重复包含问题。
递归程序【系统栈】
程序调用自身的编程技巧叫做递归
递归程序的组成部分:
*(语义信息)
1、边界条件处理【出口】“需要提前设置”
2、针对于问题的处理过程和递归过程
【递推过程】(回溯):分为向下递归和向上递归,先向下再向上。可以根据数学归纳法对递推函数进行了解过程
3、结果返回【传出参数】
【系统栈】(FILo){最大为8MB,也就是200万个整型数据,大于200万则爆栈}:先进后出
函数指针(变量)
//下面是一个分段函数
int g(int (*f1)(int), int (*f2)(int), int (*f3)(int), int x);
//int (*f1)(int)中的int为f1的返回值类型,(*f1)【f1为函数指针变量名】为传进来f1的地址,(int)为传进来的参数列表
变参函数
如scanf()函数和printf()函数等变参函数
int max_int (int a, …);
va_list类型变量可以获得a往后的参数列表,也就是上面函数的( … )
va_start函数可以定位a往后第一个参数得位置
va_arg函数可以获取下一个可变参数列表中的参数
va_end函数结束整个获取可变参数列表的动作
#include<stdio.h>
#include<inttypes.h>
#include<stdarg.h>
int max_int(int n, ...){
int ans = INT32_MIN;//定义一个整型变量为最小值
va_list arg; //arg为参数列表
va_start(arg, n);// 获取n往后的第一个参数
while(n--){
int temp = va_arg(arg, int);//获取参数列表中的下一个参数
if(temp > ans) ans = temp;
}
va_end(arg);
return ans;
}
int main(){
printf("%d\n", max_int(3, 12, 0, -2));
printf("%d\n", max_int(3, 22, 44, -2));
printf("%d\n", max_int(3, 12, 0, -2));
return 0;
}
数组
相同类型变量的集合
数组清空:int a[1000] = {0};
a[1000]中的数组名a为该数组的首地址
函数是压缩的数组,数组是展开的函数,因为两者都满足映射关系
大小:n*sizeof(types)
随机访问:a[0] + i,a + i
传参:(根据表现形式一致)一维数组–>传地址,也就是数组首地址
高维数组–>也是传首地址,但函数定义形参时地址最多可以省略一个维度,如a[] [m],(*p)[m]
字符串
空格也为一个字符
任何一个字符都对应着一个整数值
字符串必须初始化,是为了防止丢掉/0
初始化字符数组:char str[] = “zhangyangsong” / char str[size] = {‘z’, ‘h’, ‘a’, ‘n’, ‘g’};
字符串相关操作:
头文件:string.h
strlen(str) 计算字符串长度,以\0作为结束符
strcmp(str1,str2); 字符串比较整型值和字典序
strcpy(dest, src) ; 字符串拷贝
因为以上两种以\0为结束符号,\0可能被覆盖丢失,容易照成数组的越界问题
strncmp(str1,str2,n); 安全的字符串比较
strncpy(str1,str2,n); 安全的字符串拷贝
memcpy(str1,str2,n); 内存拷贝
memcmp(str1,str2,n); 内存比较
memset(str1, c, n); 内存设置,初始化数组str为c值,n为内存大小,c值需要为内存内统一值;如整型1为10000000则不是,-1为11111111则是,0为00000000则是
结构体
struct P{
char name[20];
int age;
};
直接引用:P · name
间接引用:P —>name
内存大小:【申请空间的对齐方式】以其中内存最大数据类型内存为其中最小内存的整数倍,如结构体P中name申请sizeof(int) * 20,age申请四个字节。注意,相同的类型的成员相连定义可一起计算内存
如:
struct node1 {
char a;
char b;
int c;
};//8字节;char a和char b一共占两字节,又对齐方式得4字节
struct node2 {
char a;
int c;
char b;
};//12字节
因此可以根据同类型定义来减少空间申请
共用体
共同利用同一片存储空间
union register{
struct {
unsigned char a;
unsigned char b;
unsigned char c;
} bytes;
unsigned int d;
}
上面的共用体中int占4个字节,所以struct也占4个字节,而且int和struct共占一片内存空间,根据访问类型所占字节访问成员,如:
union node{
double a;
char b;
int c;
};
上面共用体一共占8字节,如果访问内存中第一个字节则访问b;如果访问内存中前四个字节则访问c,以此类推。
大小端
小端:数字低端存在数据段低地址位
大端:数字高端存在数据段高地址位
判断大小端方式:
int is_little(){
int num = 1;//如果是小端在地址中存的是1000,如果是大段存的的是0001
return ((char*)(&num))[0];//强制转换为char指针类型。返回第一个地址,小端为1,大端为0
}
字节序:
主机/本地字节序
网络字节序
指针
变量:类型,存值,空间大小,地址
指针变量:
类型:可以是int,char…
存值:地址
空间大小:64位操作系统有8字节(8gb),32位有4字节(4gb)
地址:64位可以给2^62个字节编址,每个字节都有对应的地址,根据操作系统位数决定指针变量地址数量,指针指向的地址为全部数量地址中最小的地址,地址类型由指针变量类型决定
指向指针的指针:
指向指针的指针,存储指针的地址
p+n:
往后移n*sizeof(types)长度
等价形式转换:
*p <=> a(原始变量)
p + 1 <=> &p[1]
p->filed <=> (*p).filed <=> a.filed
函数指针:
返回值 (*函数名)(形式参数…)
(*函数名)为什么要加括号:
如int (*add)(int, int);
不加括号:int *add(int, int);这时可以理解返回值类型为int *,也可以理解为int
typedef的用法:
内建类型的重命名:如
typedef long long lint;
typedef char * pchar;
结构体类型的重命名:如
typedef struct _node{
} Node, *PNode;
函数指针命名:
typedef int (*func)(int);把函数指针变量提升为函数指针类型
main函数参数
操作系统调用main函数
int main();
int main(int argc, char*argv[]);
argc:接受命令行参数个数,argv:具体命令行参数,其中有若干行字符串,为二维数组
int main(int argc, char *argc[], char ** env);
env:环境变量,也为二维数组
int main(int argc, char *argv[],char **env){
printf("argc = %d\n", argc);
for(int i = 0; i < argc; i++){
printf("argv[%d] = %s\n", i, argv[i]);
}
for(int i = 0; env[i]; i++){
printf("env[%d] = %s\n", i, env[i]);
}
return 0;
}
预处理命令
预处理命令-宏定义
以#号开头为预处理命令
#define
宏只进行一个符号替换不做运算
宏只支持单行
定义符号常量:
#define PI 3.1415
#define MAX_N 10000
定义傻瓜表达式:
#define MAX_N(a, b) (a) > (b) ? (a) : (b)
#define S(a, b) a * b 如S(2+3, 2+3) 2+3*2+3
定义代码段:
#define P(a) {\
printf(“%d\n”, a);\
}
:反斜杠为连接符
预处理命令-预定义的宏
__DATE _ 日期:
__TIME _ 时间:
__LINE _ 行号
__FILE _ 文件名
__func _ 函数名/非标准
__FUNC _ 函数名/非标准
__PRETTY _ FUNCTION _ 更详细的函数信息/非标准
预处理命令-条件式编译
#ifdef DEBUG 是否定义了DEBUG宏
#ifndef DEBUG 是否没定义DEBUG宏
#if MAX_N == 5 宏MAX_N是否等于5
#elif MAX_N == 4 否则宏MAX_N是否等于4
#else
#endif
宏定义DEBUG
#include<stdio.h>
#define p(func){\
printf("%s = %d\n", #func, func);\
}
#define MAX(A, B) {\
__typeof(A) _A = (A);\
__typeof(B) _B = (B);\
_A > _B ? _A : _B;\
}
int main(){
int a = 7;
p(MAX(2, 3));
p(5 + MAX(2, 3));
p(MAX(2, MAX(3, 4)));
p(MAX(2, 3 > 4 ? 3 : 4));
p(MAX(a++, 6));
p(a);
return 0;
}
打印LOG宏和条件式编译
编译条件:gcc -DDEBUG LOG.c
#include<stdio.h>
#define BEBUG
#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
int main(){
int a = 123, b = 345, abcdef = 0;
//printf("[%s : %s : %d] %d\n", __FILE__, __func__, __LINE__, a);
log("%d", a);
log("%d", b);
// printf("hello world\n");
contact(abc, def) = 112233;
log("%d", abcdef);
return 0;
}
工程项目相关目录创建
linux创建目录命令:mkdir
主目录:
project
下属目录:
include:存放所有头文件【必须】
src:存放所有源文件【必须】
//编译主程序temp.cpp
①g++ -c temp.cpp //错误。没有包含head头文件
②g++ -I./include -c temp.cpp// -I./include头文件包含到系统库中,g++编译temp.cpp
//②中生成temp.o文件
g++ ./src/head //链接src中的head
g++ -I./include -c ./src/head1.cc // ./src/head1.cc为编译源文件head1.cc
g++ -I./include -c ./src/head2.cc
g++ -I./include -c ./src/head3.cc
//生成一系列对象文件.o
//使temp.o这个对象文件和src下所有的对象文件链接生成可执行程序
g++ *.o
//生成a.out这个可执行文件
bin:存放最终整个多文件编译链接生产的唯一的可执行程序
lib:存放链接库文件
makefile
(非常依赖环境)
linux创建makefile命令:touch
作用:将多个文件操作写出一个脚本,便于多文件编译
一个简版makefile:
①~④为编译源文件成对象文件
⑤为链接操作
.PHONY: clean run
//.PHONY创建了一个虚拟空间clean run
all: temp.o ./src/head1.o ./src/head2.o ./src/head3.o
g++ temp.o ./src/head1.o ./src/head2.o ./src/head3.o -o ./bin/KKB(操作⑤)
//链接上面所有对象文件成一般的a.out,且把a.out改名为KKB
temp.o: temp.cpp ./include/head1.h ./include/head2.h ./include/head3.h
g++ -I./include -c temp.cpp (操作①)
//编译temp.cpp成temp.o
./src/head1.o: ./src/head1.cc ./include/*.h
g++ -I./include -c ./src/head1.cc -o ./src/head1.o(操作②)
./src/head2.o: ./src/head2.cc ./include/*.h
g++ -I./include -c ./src/head2.cc -o ./src/head2.o(操作③)
./src/head3.o: ./src/head3.cc ./include/*.h
g++ -I./include -c ./src/head3.cc -o ./src/head3.o(操作④)
clean:
rm ./bin/KKB ./src/*.o temp.o
run:
./bin/KKB
cmakean工具
链接库
将头文件和源文件打包成链接库给别人用,别人看不到源代码,保密作用,会收到include和lib两部分
静态链接库
发给别人的链接库是静态的,以后源代码更新不会影响改变已经发给别人的链接库(.a)
linux系统打包链接库命令:
链接库取名:libxxx.a
ar -r libxxx.a head1.o head2.o head3.o
//打包对象文件成静态链接库
//ar是gnu归档工具
mv src/libxxx.a lib/
//把链接库移动到lib目录中
链接链接库
-L./lib -l (链接库名)
g++ -I./include main.cpp -L./lib -l xxx
// -L./lib -l zys为链接库路径,-L指定库路径 -l指定库名称
动态链接库
放在服务器某路径下,所有人都可以使用,源代码更新也会自动更新链接库,别人也可以使用到随时更新的链接库(.so)
linux系统打包链接库命令:
链接库取名:libxxx.so.x //为版本号
g++ head.cpp -fPIC -shared -o libxxx.so
//编译so库产生so库(libxxx.so),fpIC指编译的so库不依赖具体路径,share为生产共享格式
//g++ -fPFic -c head1.cpp head2.cpp
//g++ -share -o libxxx.so head1.o head2.o
//这两条指令和上面的等价
mv src/libxxx.a lib/
//把链接库移动到lib目录中
链接动态链接库:
g++ temp.o -o a.out -L. -l xxx
include -c ./src/head3.cc -o ./src/head3.o(操作④)
clean:
rm ./bin/KKB ./src/*.o temp.o
run:
./bin/KKB
cmakean工具
#### 链接库
将头文件和源文件打包成链接库给别人用,别人看不到源代码,保密作用,会收到include和lib两部分
##### 静态链接库
发给别人的链接库是静态的,以后源代码更新不会影响改变已经发给别人的链接库(.a)
linux系统打包链接库命令:
链接库取名:libxxx.a
```c
ar -r libxxx.a head1.o head2.o head3.o
//打包对象文件成静态链接库
//ar是gnu归档工具
mv src/libxxx.a lib/
//把链接库移动到lib目录中
链接链接库
-L./lib -l (链接库名)
g++ -I./include main.cpp -L./lib -l xxx
// -L./lib -l zys为链接库路径,-L指定库路径 -l指定库名称
动态链接库
放在服务器某路径下,所有人都可以使用,源代码更新也会自动更新链接库,别人也可以使用到随时更新的链接库(.so)
linux系统打包链接库命令:
链接库取名:libxxx.so.x //为版本号
g++ head.cpp -fPIC -shared -o libxxx.so
//编译so库产生so库(libxxx.so),fpIC指编译的so库不依赖具体路径,share为生产共享格式
//g++ -fPFic -c head1.cpp head2.cpp
//g++ -share -o libxxx.so head1.o head2.o
//这两条指令和上面的等价
mv src/libxxx.a lib/
//把链接库移动到lib目录中
链接动态链接库:
g++ temp.o -o a.out -L. -l xxx