文章目录
- 一、C++函数指针和函数类型
- 二、异步客户端和同步客户端
- 三、回调函数
- 四、 无锁队列
- 四、#if 0 ... #endif 注释
- 五、防止头文件重复引用
- 六、C++使用初始化列表来初始化字段
- 七、main函数标准写法
- 八、C++异常处理
- 九、std::nothrow
- 十、C++中的一些类型
- 十一、将数据存储在数组中,并转化为string类型
- 十二、链表问题: 虚拟节点dummy
- 十三、预编译指令
- 十四、C++枚举类型
- 十五、内联函数
- 十六、动态库和静态库
- 十七、.c和.cpp文件区别
- 十八、序列化
- map、hash_map、unordered_map的区别
- 十九、stringstream ss()
- 二十、getchar()、getline(cin, s)
- 参考文章
一、C++函数指针和函数类型
1. 定义
- 函数指针指向的是函数而非对象。和其他指针类型一样,函数指针指向某种特定类型
- 函数类型由它的返回值和参数类型决定,与函数名无关
bool length_compare(const string &, const string &);
上述函数类型是:bool (const string &, const string &)
;
上述函数指针pf:bool (*pf)(const string &, const string &)
;
2. 使用函数指针
- 当把函数名作为一个值使用时,该函数自动的转换成指针,如:
pf = length_compare <=>等价于pf = &length_compare
3. 函数指针形参
- 函数类型不能定义为形参,但是形参可以是指向函数的指针
- 函数作为实参使用时,会自动的转换成函数指针
4. 返回指向函数的指针
返回执行函数类型的指针。和函数参数不同,编译器不会自动地将函数返回类型当作指针类型处理,必须显示的将返回类型指定为指针,如下
using F = int(int*, int); F是函数类型
using PF = int(*)(int*,int); PF是函数指针类型
// 需要指定返回类型是指针
F f1(int); //错误: F是函数类型
PF f1(int); //正确: PF是函数指针类型
二、异步客户端和同步客户端
1. 同步客户端
比如一个连接有两个请求,请求1 和 请求2,请求1 先发起请求,请求2后发起请求
则请求2 要等待请求1 响应完成才能接收到响应。
举个枣子,httpclient 发送get请求,线程会一致阻塞,直到有响应结果。
2. 异步客户端
比如一个连接有两个请求,请求1 和 请求2,请求1 先发起请求,请求2后发起请求,请求1 和 请求2 可以并发的获取响应。
举个枣子,asynhttpclient 发送get请求,线程不会阻塞,程序往下走。
三、回调函数
回调函数就是一个通过函数指针调用的函数,如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
四、 无锁队列
1. 定义
(单读单写)
两个线程同步操作某种数据时,不能加锁;因为加锁是会拖慢效率,造成延时。
2. 什么场景需要用到无锁队列?
生产者和消费者模型,要求生产者生产的同时,进行消费,这就要求足够的低延时,此时可以使用无锁队列
3. 无锁队列的实现
#define MAX_NUMBER 1000
#define OK 0
#define ERROR -1
typedef struct _QUEUE_DATA
{
int iData[MAX_NUMBER];
int head;
int tail;
}QUEUE_DATA;
QUEUE_DATA* pHead = new QUEUE_DATA;
pHead->head = 0;
pHead->tail = 0;
//从队尾加入数据,队列是一个环形队列
int push_data(QUEUE_DATA* pHead, int data)
{
if(pHead == NULL || (pHead->head ==((pHead->tail +1) % MAX_NUMBER)))
{
return ERROR;
}
pHead->iData[pHead->tail] = data;
pHead->tail = (pHead->tail+1) % MAX_NUMBER;
return OK;
}
int pop_data(QUEUE_DATA* pHead)
{
if(pHead == NULL || (pHead->head == pHead->tail))
{
return ERROR;
}
pHead->iData[pHead->head] = 0;
pHead->head = (pHead->head+1)% MAX_NUMBER;
return OK;
}
四、#if 0 … #endif 注释
块注释符(/…/)是不可以嵌套使用的。
可以使用 #if 0 … #endif 来实现注释,且可以实现嵌套,格式为:
#if 0
code
#endif
你可以把 #if 0 改成 #if 1 来执行 code 的代码。
这种形式对程序调试也可以帮助,测试时使用 #if 1 来执行测试代码,发布后使用 #if 0 来屏蔽测试代码。
#if 后可以是任意的条件语句。
五、防止头文件重复引用
在C/C++中,在使用预编译指令**#include**的时候,为了防止重复引用造成二义性,通常有两种方式
第一种是 #ifndef 指令防止代码块重复引用,比如说
#ifndef _CODE_BLOCK // _CODE_BLOCK是头文件的文件名大写
#define _CODE_BLOCK
// code
#endif// _CODE_BLOCK
第二种就是 #pragma once指令,在想要保护的文件开头写入
#pragma once
同一个文件不会被包含多次。这里所说的”同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。无法对一个头文件中的一段代码作#pragma once声明,而只能针对文件
缺点是如果某个头文件有多份拷贝,此方法不能保证它们不被重复包含
总结:#ifndef可以针对一个文件中的部分代码,而#pragma once只能针对整个文件。相对而言,#ifndef更加灵活,兼容性好,#pragma once操作简单,效率高
六、C++使用初始化列表来初始化字段
使用初始化列表来初始化字段:
Line::Line( double len): length(len)
{
cout << "Object is being created, length = " << len << endl;
}
上面的语法等同于如下语法:
Line::Line( double len)
{
length = len;
cout << "Object is being created, length = " << len << endl;
}
七、main函数标准写法
main( int argc, char* argv[], char **env )
是UNIX、Linux以及Mac OS*
操作系统中C/C++的main函数标准写法
第一个参数:统计程序运行时发送给main函数的命令行参数的个数,在VS中默认值为1*
第二个参数:用来存放指向的字符串参数的指针数组,每一个元素指向一个参数。各成员含义如下:
argv[0]指向程序运行的全路径名
argv[1]指向在DOS命令行中执行程序名后的第一个字符串
argv[2]指向执行程序名后的第二个字符串
argv[3]指向执行程序名后的第三个字符串
argv[argc]为NULL
第三个参数:char**型的env,为字符串数组。env[]的每一个元素都包含ENVVAR=value形式的字符串,
其中ENVVAR为环境变量,value为其对应的值。平时使用到的比较少
八、C++异常处理
我们通常希望自己编写的程序能够在异常的情况下也能作出相应的处理,而不至于程序莫名其妙地中断或者中止运行了。在设计程序时应充分考虑各种异常情况,并加以处理。
在C++中,一个函数能够检测出异常并且将异常返回,这种机制称为抛出异常。
当抛出异常后,函数调用者捕获到该异常,并对该异常进行处理,我们称之为异常捕获。
C++新增throw关键字用于抛出异常,新增catch关键字用于捕获异常,新增try关键字尝试捕获异常。通常将尝试捕获的语句放在 try{ } 程序块中,而将异常处理语句置于 catch{ } 语句块中
抛出异常的基本语法:
throw 表达式;
抛出异常后需要捕获异常以及异常处理程序,其基本语法如下:
try
{
//可能抛出异常的语句
}
catch (异常类型1)
{
//异常类型1的处理程序
}
catch (异常类型2)
{
//异常类型2的处理程序
}
// ……
catch (异常类型n)
{
//异常类型n的处理程序
}
由try程序块捕获throw抛出的异常,然后依据异常类型运行catch程序块中的异常处理程。catch程序块顺序可以是任意的,不过均需要放在try程序块之后。
在C语言中,异常通常是通过函数返回值获得,但这样一来,函数是否产生异常则需要通过检测函数的返回值才能得知。而在C++中,当函数抛出一个返回值时,即使不用try和catch语句,异常还是会被处理的,系统会自动调用默认处理函数unexpected来执行。
九、std::nothrow
在内存不足时,new (std::nothrow)并不抛出异常,而是将指针置NULL
分配失败是非常普通的,它们通常在植入性和不支持异常的可移动的器件中发生更频繁。因此,应用程序开发者在这个环境中使用nothrow new来替代普通的new是非常安全的
p = new(std :: nothrow)char[1024*1024];
p = new(std::nothrow)char[2047 *1024 *1024];
十、C++中的一些类型
1. C++ 什么是有符号,什么是无符号
整型有无符号(unsigned)和有符号(signed)两种类型。
在默认情况下声明的整型变量都是有符号的类型(char有点特别),如果需声明无符号类型的话就需要在类型前加上unsigned.
区别:无符号类型能保存2倍于有符号类型的数据。
比如16位系统中一个int能存储的数据的范围为-32768 ~ 32767,而unsigned能存储的数据范围则是0~65535。在一些不可能取值为负数的时候,可以定义为unsigned,
2. auto类型
auto被解释为一个自动存储变量的关键字,也就是申明一个临时的变量内存
3. size_t类型
size_t是一些C/C++标准在stddef.h中定义的。这个类型足以用来表示对象的大小。
size_t的真实类型与操作系统有关。
在32位架构中被普遍定义为:
typedef unsigned int size_t;
而在64位架构中被定义为:
typedef unsigned long size_t;
size_t在32位架构上是4字节,在64位架构上是8字节,在不同架构上进行编译时需要注意这个问题。而int在不同架构下都是4字节,与size_t不同;且int为带符号数,size_t为无符号数。
为什么有时候不用int,而是用size_type或者size_t
与int固定四个字节不同有所不同,size_t的取值range是目标平台下最大可能的数组尺寸,一些平台下size_t的范围小于int的正数范围,又或者大于unsigned int. 使用Int既有可能浪费,又有可能范围不够大。
4. int16, int32, int64
int16 | 16位整数(16bit integer) | 相当于short 占2个字节 | -32768 ~ 32767 |
---|---|---|---|
int32 | 32位整数(32bit integer) | 相当于 int 占4个字节 | -2147483648 ~ 2147483647 |
int64 | 64位整数(64bit interger) | 相当于 long long 占8个字节 | -9223372036854775808 ~ 9223372036854775807 |
Byte | byte(unsigned char) | 0 ~ 255 | |
WORD | unsigned short | 0 ~ 65535 |
5. unit8_t、unit16_t、unit32_t、unit64_t
unit8_t | 无符号1个字节的整型 |
---|---|
unit16_t | 无符号2个字节的整型 |
unit32_t | 无符号4个字节的整型 |
unit64_t | 无符号8个字节的整型 |
注:一个字节有8位。
十一、将数据存储在数组中,并转化为string类型
// 将数据存储到数组中
char buff[100] = {0};
sprintf((char*)buff, " height = %-10f x = %-10f y = %-10f angle = %-10f",x,y,z,angle);
// 将数组转化为string类型
int size_buff = sizeof(buff) / sizeof(char);
string str = "";
for(int x = 0; x < size_buff; x++)
{
str = str + buff[x];
}
十二、链表问题: 虚拟节点dummy
在链表操作中,使用一个dummy结点,可以少掉很多边界条件的判断。
在链表的头部加入一个哨兵,然后连上head节点
之后就把head节点当做普通节点,不用单独考虑了
ListNode *dummy = new ListNode(-1);
dummy -> next = head;
head = dummy;
最后返回
return head->next;
十三、预编译指令
c语言中条件编译相关的预编译指令,包括#define、 #undef、 #ifndef、#if、#elif、#else、#endif、defined
#define 定义一个预处理宏
#undef 取消宏的定义
#if 编译预处理中的条件命令,相当于C语法中的if语句
#ifdef 判断某个宏是否被定义,若已定义,执行随后的语句
#ifndef 与#ifdef相反,判断某个宏是否未被定义
#elif 与#if、#ifdef、#ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else if
#else 与#if、#ifdef、#ifndef对应,若这些条件不满足,则执行#else之后的语句,相当于C语法中的else
#endif #if、#ifdef、#ifndef这些命令的结束标志
defined 与#if、#elif配合使用,判断某个宏是否被定义
defined(name): 若宏被定义,则返回1,否则返回0。
#if 条件语句
程序段1 //如果条件语句成立,那么就编译程序段1
#endif
程序段2//如果条件不语句成立,那么就编译程序段2
#ifndef x//先测试x是否被宏定义过
#define 程序段1 //如果x没有被宏定义过,那么就编译程序段1
#endif
程序段2 //如果x已经定义过了则编译程序段2的语句,“忽视”程序段1。
#ifdef x //先测试x是否被宏定义过
程序段1 //如果x被宏定义过,那么就编译程序段1
#endif
程序段2 //如果x没有被定义过则编译程序段2的语句,“忽视”程序段1。
if就是判断语句,不是预编译指令
十四、C++枚举类型
枚举类型是C++中的一种派生数据类型,它是由用户定义的若干枚举常量的集合。
定义格式为:
enum <类型名> { <枚举常量表> };
例子如下:
enum color_set1 {RED, BLUE, WHITE, BLACK}; // 定义枚举类型color_set1
enum week {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; // 定义枚举类型week
编译系统会为每个枚举常量指定一个整数值,默认情况下,整数序号从0开始。
可以在定义枚举类型时为部分或全部枚举常量指定整数值,在指定值之前的枚举常量仍按默认情况方式取值,而指定值之后的枚举常量按依次加1的原则取值。各枚举常量的值可以重复。例如:
enum fruit_set {apple, orange, banana=1, peach, grape}
//枚举常量apple=0,orange=1, banana=1,peach=2,grape=3。
enum week {Sun=7, Mon=1, Tue, Wed, Thu, Fri, Sat};
//枚举常量Sun,Mon,Tue,Wed,Thu,Fri,Sat的值分别为7、1、2、3、4、5、6。
枚举常量只能以标识符形式表示,而不能是整型、字符型等文字常量。
例如:
enum letter_set {'a','d','F','s','T'}; //枚举常量不能是字符常量
enum year_set{2000,2001,2002,2003,2004,2005}; //枚举常量不能是整型常量
可改为以下形式则定义合法:
enum letter_set {a, d, F, s, T};
enum year_set{y2000, y2001, y2002, y2003, y2004, y2005};
- 枚举类型可以直接输出,但不能直接输入。如:cout >> color3; // 非法
- 不能直接将常量赋给枚举变量。 如:color = 1; // 非法
- 不同类型的枚举变量之间不能相互赋值。如:color1 = color3; // 非法
- 枚举变量的输入输出一般都采用switch语句将其转化为字符或字符串;枚举类型数据的其他处理也往往应用switch语句,以保证程序的合法性和可读性
十五、内联函数
1. 作用
用来降低程序的运行时间,当内联函数收到编译器的指示时,即可发生内联:编译器将使用函数的定义体来代替函数调用语句,这种替代行为发生在编译阶段而非程序运行阶段,通过内联函数,编译器不需要跳转到内存其他地址去执行函数调用,也不需要保留函数调用时的现场数据。
2. 用法
定义函数时,在函数的最前面以关键字inline
声明函数,即可使函数称为内联声明函数。
虚函数不允许内联。
3. 优缺点分析
优点:
- 通过避免函数调用所带来的开销来提高程序运行速度
- 当函数调用发生时,它节省了变量弹栈、压栈的开销(变量压栈,在参数列表中从右到左)
- 避免了一个函数执行完返回原现场的开销
- 通过将函数声明为内联,可以将函数定义放在头文件
缺点:
- 因为代码的扩展,内联函数增大了可执行程序的体积
- C++内联函数的展开是中编译阶段,这就意味着如果你的内联函数发生了改动,那么就需要重新编译代码
- 当把内联函数放在头文件中,头文件的信息会变多
- 有时候内联函数并不受到青睐,比如在嵌入式系统中,嵌入式系统的存储约束可能不允许体积很大的可执行程序
4. 什么时候使用
- 当对程序执行性能有要求时
- 当你想宏定义一个函数时
- 在类内部定义的函数会默认声明为inline函数,这有利于 类实现细节的隐藏
十六、动态库和静态库
静态库
一般扩展名为(.a或.lib),这类的函数库通常扩展名为libxxx.a或xxx.lib
这类库在编译的时候会直接整合到目标程序中,所以利用静态函数库编译成的文件会比较大,这类函数库最大的优点就是编译成功的可执行文件可以独立运行,而不再需要向外部要求读取函数库的内容;但是从升级难易度来看明显没有优势,如果函数库更新,需要重新编译
动态库
一般扩展名为(.so或.dll),这类的函数库通常扩展名为libxxx.so或xxx.dll
与静态函数库被整个捕捉到程序中不同,动态函数库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;也就是可执行文件无法单独运行。这样从产品功能升级角度方便升级,只有替换对应动态库即可,不必重新编译整个可执行文件
区别
该库是否被编译进目标(程序)内部
十七、.c和.cpp文件区别
一、指代不同
1、.c:表示C的源程序。
2、.cpp:表示C++的源程序。
二、变量声明不同
1、.c:c中变量声明和代码是分开的,必须在函数开始处声明。
2、.cpp:c++变量可以在任意处声明,只要保证先声明后使用就行。
三、指针类型不同
1、.c:void指针可以给任意类型指针赋值。
2、.cpp:必须先进行强制数据类型转换再赋值。
十八、序列化
- 序列化就是将对象序列化为二进制形式(字节数组),一般也将序列化称为编码(Encode),主要用于网络传输、数据持久化等;
- 反序列化(deserialization)则是将从网络、磁盘等读取的字节数组还原成原始对象,以便后续业务的进行,一般也将反序列化称为解码(Decode),主要用于网络传输对象的解码,以便完成远程调用。
影响序列化性能的关键因素
- 序列化后的码流大小(网络带宽的占用);
- 序列化的性能(CPU资源占用);
- 是否支持跨语言(异构系统的对接和开发语言切换)
Protobuf
它将数据结构以.proto文件进行描述,通过代码生成工具可以生成对应数据结构的POJO对象和Protobuf相关的方法和属性。
优点:
- 序列化后码流小,性能高
- 结构化数据存储格式(XML JSON等)
- 通过标识字段的顺序,可以实现协议的前向兼容
- 结构化的文档更容易管理和维护
缺点
- 需要依赖于工具生成代码
- 支持的语言相对较少,官方只支持Java 、C++ 、Python
适用场景
- 对性能要求高的RPC调用
- 具有良好的跨防火墙的访问属性
- 适合应用层对象的持久化
map、hash_map、unordered_map的区别
参考网址:map 学习(下)
1. 头文件
- map:
#include<map>
- hash_map:
#include<hash_map>
- unordered_map:
#include<unordered_map>
2. 内部实现机理
- map: 内部实现了一个红黑树,该结构具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每个节点都代表着map的一个元素,因此,对于map进行的查找、删除、添加等操作都相当于是对红黑树进行这样的操作,故红黑树的效率决定了map的效率,map只需要提供比较函数(一般为小于函数)即可完成比较
- hash_map: 需要提供hash函数,以及等于函数
- unordered_map:内部实现了一个Hash表,所以其元素的排列顺序是杂乱无章的
3. 优缺点
map
优点
- 有序性;
- 效率高,map的很多操作在log n的时间复杂度就可以实现
缺点
- 空间占用率高,map内部实现了红黑树,虽然提高了运行效率,但是每个节点都需要额外保存父节点,子节点以及红/黑性质,使得每个节点都占用大量空间
总结
- 适用于具有顺序要求的问题
- map一般就是用在数据量小于1000或者对内存使用要求比较高的情况下
hash_map
优点
- 查找速度会比map快,而且查找速度基本和数据量大小无关,属于常数级别(但不能说一定比 map的log n 级别快,因为hash函数本身也有耗时)
缺点
- 空间占用多,如果对内存使用很严格,需要认真考虑是否使用hash_map;特别是hash_map对象特别多时,更加难以控制;
总结
适用于对效率要求较高的环境
unordered_map
优点
- 内部实现了Hash表,所以查找速度很快
缺点
- Hash表的建立比较耗时
总结
适用于查找问题
4. 选用
总结下来就是三个权衡点:查找速度、数据量、内存使用
5. hash_map与unordered_map的区别
hash_map和unordered_map底层都是hash table实现的,unordered_map性能比hash_map要好很多,并且unordered_map已经成为标准,不建议再使用hash_map。
两者的rehash不同
- unordered_map:每次insert时,先判断最后需要的数据大小是否超过了预设的最大值_M_next_resize,是则判断需要的bucket数是否大于当前的bucket数,如果是则需要rehash操作了。
- rehash的时候先将当前的bucket数乘以负载因子得到新的bucket数,在与__min_bkts对比得到最大的bucket数更新到__min_bkts中。接下来,再从__prime_list中选出比__min_bkts大的最小素数(素数的hash效果比较好),更新_M_max_load_factor后将该素数返回。
- 而hash_map:更新bucket数目为需要的数目就行了,而不会像unordered_map那样子进行数量的优化。
十九、stringstream ss()
用来进行流的输入输出
cplusplus官方版本:
// swapping ostringstream objects
#include <string> // std::string
#include <iostream> // std::cout
#include <sstream> // std::stringstream
int main () {
std::stringstream ss;
ss << 100 << ' ' << 200; // 将100 200放进流中
int foo,bar;
ss >> foo >> bar; // 将流中的数据输出到foo、bar中
std::cout << "foo: " << foo << '\n';
std::cout << "bar: " << bar << '\n';
return 0;
}
/* 输出
foo: 100
bar: 200
*/
用例:从string对象str中读取字符串。遇空格结束
#include<iostream>
#include<sstream>
using namespace std;
int main()
{
string str = "hello world";
cout << str << endl;
stringstream ss(str); // 将str复制到ss
string abc;
while(ss >> abc) //相当于输入一个个的单词
{
cout << abc << endl;
}
return 0;
}
二十、getchar()、getline(cin, s)
getchar(); // 从标准输入 stdin 获取一个字符
getline(cin, s); // 接受一个字符串,可以接受空格并输出
举例如下:
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s, str;
// cin >> s ; // hello world
// cout << s << endl; // hello
getline(cin, str); // hello world
cout << str << endl; // hello world
return 0;
}