C/C++随笔
C
文件操作
文件的打开
fopen():打开文件
文件的关闭
fclose():关闭文件
文件的读写
fgetc():读取一个字符 int fgetc ( FILE * stream );
fputc():写入一个字符 int fputc( int c, FILE *stream );
fgets():读取一个字符串 char * fgets ( char * str, int num, FILE * stream );
fputs():写入一个字符串 int fputs( const char *string, FILE *stream );
fprintf():写入格式化数据
fscanf():格式化读取数据
fread():读取数据 size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
fwrite():写入数据 size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
文件状态检查
feof():文件是否结束
ferror():文件读/写是否出错
clearerr():清除文件错误标志
ftell():文件指针的当前位置
文件指针定位
rewind():把文件指针移到开始处
fseek():重定位文件指针
二维数组
数组指针 != 指针数组
// [] 优先级比 * 高 |
函数指针与回调函数
函数指针
// 普通函数声明 |
|
回调函数 Callback Function
函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。
简单讲:回调函数是由别人的函数执行时调用你实现的函数。
回调函数通常是在特定的事件或条件发生时,由另外的一方调用的,用于对该事件或条件进行响应。其意义就在于解耦。
int rfun_call(int (*pfun)(unsigned), unsigned x) |
|
I/O 多路复用
select
fd_set 使用数组实现
1.fd_size 有限制 1024 bitmap
fd【i】 = accept()
2.fdset不可重用,新的fd进来,重新创建
3.用户态和内核态拷贝产生开销
4.O(n)时间复杂度的轮询
成功调用返回结果大于 0,出错返回结果为 -1,超时返回结果为 0
具有超时时间
poll
基于结构体存储fd
struct pollfd{
int fd;
short events;
short revents; //可重用
}
解决了select的1,2两点缺点
epoll
解决select的1,2,3,4
不需要轮询,时间复杂度为O(1)
epoll_create 创建一个白板 存放fd_events
epoll_ctl 用于向内核注册新的描述符或者是改变某个文件描述符的状态。已注册的描述符在内核中会被维护在一棵红黑树上
epoll_wait 通过回调函数内核会将 I/O 准备好的描述符加入到一个链表中管理,进程调用 epoll_wait() 便可以得到事件完成的描述符
两种触发模式:
LT:水平触发
当 epoll_wait() 检测到描述符事件到达时,将此事件通知进程,进程可以不立即处理该事件,下次调用 epoll_wait() 会再次通知进程。是默认的一种模式,并且同时支持 Blocking 和 No-Blocking。
ET:边缘触发
和 LT 模式不同的是,通知之后进程必须立即处理事件。
下次再调用 epoll_wait() 时不会再得到事件到达的通知。很大程度上减少了 epoll 事件被重复触发的次数,
因此效率要比 LT 模式高。只支持 No-Blocking,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
动态静态链接库
分类
静态库(.a): 在链接期间被应用程序直接链接进可执行文件
动态链接库(.so): 动态库还分为两种用法:
a) 应用程序运行期间链接动态库,但是在编译期间声明动态库的存在,也就是说这种动态库必须在编译时对编译器可见,但编译器却不将此种库编译进可执行文件;b) 在运行期间,动态加载和卸载的库,使用动态加载方法加载。这种库的形式跟动态链接没有本质区别,区别是在调用时,是由用户程序决定何时链接的,而不是由系统链接器自动链接
编译库
动态库 |
使用库
libxxx.a |
引入对应的头文件即可使用(相当于.a, .so 隐藏了具体实现)
动态库使用方法
动态链接库的使用需要库的开发者提供生成的.lib文件和.dll文件。或者只提供dll文件。使用时只能使用dll中导出的函数,未导出的函数只能在dll内部使用。
Dll的调用有显示连接和隐式连接两种:隐式连接需要三个东西,分别是*.h头文件,lib库(动态的), DLL库;显示连接只需要.dll文件即可。
显式(动态)加载
这种方式通过调用API函数来完成对DLL的加载与卸载,能更加有效地使用内存,在编写大型应用程序时往往采用此方式。
显式加载优点
- 灵活,可以在需要的时候用
LoadLibrary进行加载,在不需要的时候用FreeLibrary进行卸载,这样可以不必占用内存。 - 可以在没有dll时候发现,而不致程序报错。
- 加载程序中有条件才运行的库。
- 热更新,在不停止程序的前提下进行更新。
显式加载缺点
- 复杂一些,需要显示获得函数地址。
dll没有对应的lib文件,此时只能进行动态加载。
加载方法
- 使用Windows API函数Load Library将
DLL模块映像到进程的内存空间,对DLL模块进行动态加载。 - 使用
GetProcAddress函数得到要调用DLL中的函数的指针。 - 不用
DLL时,用FreeLibrary函数或者AfxFreeLibrary函数从进程的地址空间显式卸载DLL。
- 动态(显式)调用,只需要
xxx.dll
Windoiws环境:void DynamicUse()
{
// 运行时加载DLL库,静态加载
HMODULE module = LoadLibrary("DLLTest1.dll");
if (module == NULL)
{
printf("加载DLLTest1.dll动态库失败\n");
return;
}
typedef int(*AddFunc)(int, int); // 定义函数指针类型
AddFunc add;
// 导出函数地址
add = (AddFunc)GetProcAddress(module, "add");
int sum = add(100, 200);
printf("动态调用,sum = %d\n",sum);
}
linux环境: Linux中的so库实现库的动态加载。最终链接时,使用-ldl**。
|
编译链接gcc dmain.c -ldl -o main
- 静态(隐式)调用,还需要
xxx.h
windows环境:
// 先将lib与dll导入项目
extern "C" int add(int a, int b);
// 静态调用DLL库
void StaticUse()
{
int sum = add(10, 20);
printf("静态调用,sum = %d\n", sum);
}
linux环境:
设置LD_LIBRARY_PATH 参数指定路径,否则无法运行可执行程序export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. // .表示当前目录
gcc main.c -L./ -lmath -o main |
查看命令
nm命令
有时候可能需要查看一个库中到底有哪些函数,nm****命令可以打印出库中的涉及到的所有符号。库既可以是静态的也可以是动态的。nm列出的符号有很多,常见的有三种:
- 一种是在库中被调用,但并没有在库中定义(表明需要其他库支持),用U表示;
- 一种是库中定义的函数,用T表示,这是最常见的;
- 一种是所谓的弱态”符号,它们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示。
$nm libhello.a
ldd命令
ldd****命令可以查看一个可执行程序依赖的共享库,例如我们编写的四则运算动态库依赖下面这些库:
设计模式
常见设计模式
单例模式:确保一个类只有一个实例,并提供全局访问点。 |
PIMPL
PIMPL(Pointer to Implementation)是一种设计模式,用于减少类之间的依赖关系和编译时间。它的基本思想是将类的实现细节封装在一个独立的类中,然后将这个类的指针作为类的成员变量来使用,从而达到降低编译时耦合度的目的。
具体来说,PIMPL 模式将公共接口与私有实现分离开来。公共接口是一个完整的类定义,而私有实现则是由一个独立的类进行实现,这个独立的类被称为 PIMPL 类。PIMPL 类只能在类的实现文件中引用,在类的头文件中并不可见。这样做可以有效地隐藏内部实现细节,并且在修改实现时不会影响用户代码的编译。
使用 PIMPL 模式可以避免头文件的相互包含,从而减少编译时间。此外,当需要修改类的实现时,只需要修改 PIMPL 类的定义,而不需要重新编译使用这个类的客户端代码。这使得代码的维护更加容易,也提高了代码的可重用性。
网络编程
客户端
graph TD |
服务器端
graph TD |
C++
C++ 是一种面向对象的编程语言,它具有以下特性:
- 面向对象:C++ 支持面向对象的编程范式,包括封装、继承和多态。
- 泛型编程:C++ 支持泛型编程,可以使用模板来实现通用的数据类型和算法。
- 高效性:C++ 是一种高效的语言,它可以直接操作内存,而不需要通过虚拟机或解释器来执行代码。
- 多重继承:C++ 支持多重继承,可以从多个基类中继承属性和方法。
- 强类型:C++ 是一种强类型语言,可以在编译时检查类型错误。
- 高级内存管理:C++ 允许程序员直接管理内存,包括动态分配和释放内存。
- 运算符重载:C++ 允许程序员重载运算符,可以自定义对象之间的运算。
- 内联函数:C++ 支持内联函数,可以将函数的代码直接插入到调用它的地方,提高执行效率。
- 异常处理:C++ 支持异常处理,可以在程序出现异常时进行处理。
- 标准库:C++ 标准库提供了丰富的函数和类,包括容器、算法、输入输出和字符串处理等。
左值与右值
左值右值
x++ 是右值;++x 是左值。
笼统区分左右值是看在等号左边还是右边。但凡能取地址就是左值;通常字面量是一个右值,字符串字面量除外(auto p = &"hello";,其能取地址);
左值引用
int a = 0; int &b = a;
- 常量左值引用既可以接收左值又可以接收右值。但无法修改内容,强制类型转换除外。
右值引用
int &&x = 1;
作用: 延长右值的声明周期,减少对象的复制,提升程序性能。
移动语义
移动构造器,只有指针的替换,没有内存的申请和释放。形参要使用右值引用,因为资源被转移而不会发生深拷贝。
移动构造器要加noexcept保证函数发生异常时终止,因为移动时发生异常会造成源对象与目标对象都不完整的情况。
值类别
graph TD |
STL容器
需要引入相应的头文件
1 vector<int>vec;//定义vector,常用
2 list<int>lis;
3 deque<int>deq;
4 stack<int>sta;//定义栈,常用
5 queue<int>que;//定义栈,常用
//vector的定义 |
inline 与 static
非类的成员函数以及成员变量 |
虚函数与纯虚函数
1、纯虚函数声明如下: virtual void funtion1()=0; 纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。 |
lambda表达式
[capture-list] (parameter-list) -> return-type { function-body } |
函数隐藏,函数重载,函数重写,多态性
函数隐藏/重定义
基类和派生类函数名一致但参数列表无所谓的非虚函数,派生类默认访问派生类函数,也可以通过添加类限定符访问基类函数,例如: B b; b.A::show();
函数重载
同一个类中,函数名一致,但参数列表不一致,可看作是两个独立的函数。
函数重写
基类使用 virtual 关键字修饰成员函数,派生类重写此函数,函数名和参数列表均一致。
多态性
派生类存在函数重写,即重写了virtual修饰的函数,且存在基类指针指向派生类对象的情况
a编译时多态性:通过重载函数实现 。
b运行时多态性:通过虚函数实现。
dynamic_cast
基类必须存在虚函数才能使用,其本质是查询虚函数表。
不能保证转换是否安全,但可以给出结果,指针或nullptr
指针强行转换
- 静态类型检查失效:在编译时,编译器只能检查指针的静态类型,而不能检查指针所指向对象的动态类型。因此,如果将一个父类指针强制转换为子类指针,但实际指向的对象不是子类对象,那么在运行时可能会发生未定义行为,如访问非法内存地址、引起段错误等。
- 数据截断:由于子类对象可能比父类对象更大,因此将子类指针强制转换为父类指针可能会导致数据截断。例如,如果一个子类对象有一个额外的成员变量,但将其强制转换为父类指针,那么访问该成员变量可能会导致未定义行为。
- 对象切片:当将子类对象赋值给父类对象时,只会复制父类部分的成员变量,而子类部分的成员变量将被忽略。这种现象被称为“对象切片”,可能会导致程序错误。
因此,应该避免在 C++ 中进行父类和子类指针之间的强制类型转换,或者在进行转换之前进行动态类型检查,以确保指针所指向的对象的动态类型与转换后的类型相同。
type & type_info
typeid晕眩符返回type_info类的引用.(头文件
重载了 == 和!==运算符,用于类型的比较
可以用在多态场景,在运行阶段识别对象的数据类型
泛编程
自动推导类型,函数模板,类模板
auto关键字
自动推导类型
用途如下:
函数指针
模板中声明依赖模板参数的变量
模板中依赖模板参数的返回值
lambda表达式
函数模板
关键字可以用 typename 和 class,函数模板用typename,类模板用class
template <typename T> |
不能为虚函数和析构函数使用模板
使用函数模板时,尽量显示制定类型,没用到T时,必须明确数据类型
函数模板的具体化(尖括号内不写类型)
template<> |
优先级
具体化模板优先于常规模板,普通函数优先于具体化模板。
如果希望使用函数模板,可以用空模板参数Swap<>(a,b)强制使用函数模板
如果函数模板能产生更好地匹配,将优先于非函数模板
==普通函数模板的声明和定义都要写在头文件中,而具体化模板可以分开写==
decltype推导类型
decltype(expression) var
short a = 5;
decltype(a) da; da->short
decltype((a)) da=a; (引用要初始化)da->short &
decltype(++a) da; da->short&
decltype(func()) da; da->func的返回值类型
decltype(func) da; da->int da(), 即func的函数类型
decltype((func)) da = func;(引用要初始化) da->int (&da)(), 即函数的引用类型
decltype(func) *da = func; da-> int (*da)(), 即函数指针类型;
decltype & auto 区别
decltype(func()) da; // 不会执行函数 |
类模板
template <class T> |
智能指针
调用DLL文件
在C++中调用DLL文件,可以使用以下步骤:
- 在C++代码中包含Windows.h头文件。
- 使用LoadLibrary函数加载DLL文件。该函数返回一个句柄,可以用于后续的函数调用。
- 使用GetProcAddress函数获取DLL文件中的函数地址。该函数需要传递DLL文件的句柄和函数名。
- 将获取的函数地址转换为函数指针,并调用该函数。可以使用typedef来定义函数指针类型,以便更方便地调用函数。
下面是一个简单的示例代码,演示如何在C++中调用DLL文件中的函数:
|
在上面的示例代码中,我们首先使用LoadLibrary函数加载了一个名为MyDll.dll的DLL文件。然后使用GetProcAddress函数获取了该文件中名为Add的函数的地址,并将其转换为函数指针类型AddFunc。最后,我们调用了该函数并输出了结果。最后使用FreeLibrary函数卸载了DLL文件。
文件操作
输出流:ostream (output stream)
输出流:istream (input stream)
输入输出流:iostream
写操作(输出)的文件类:ofstream
读操作(输入)的文件类:ifstream
可同时读写操作的文件类:fstream (file stream)
//对文件的读取示例 |
输入输出流
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0),用于关闭C/C++的标准流的同步,还将cin,cout与cerr分离,避免cin,cout同步问题
String
如果希望在最终读入的字符串中保留空格,可以使用getline函数
getline(cin , s1);
1.字符串构造函数
string str1; //生成空字符串 |
2.string大小和容量
- size()和length():返回string对象的字符个数,他们执行效果相同。
- max_size():返回string对象最多包含的字符数,超出会抛出length_error异常
- capacity():重新分配内存之前,string对象能包含的最大字符数
string s("1234567"); |
3.string的字符串比较
- C ++字符串支持常见的比较操作符(>,>=,<,<=,==,!=),甚至支持string与C-string的比较(如 str<”hello”)。在使用>,>=,<,<=这些操作符的时候是根据“当前字符特性”将字符按字典顺序进行逐一得 比较。字典排序靠前的字符小,比较的顺序是从前向后比较,遇到不相等的字符就按这个位置上的两个字符的比较结果确定两个字符串的大小(前面减后面)
同时,string (“aaaa”) < string(“aaaaa”)。 - 另一个功能强大的比较函数是成员函数compare()。他支持多参数处理,支持用索引值和长度定位子串来进行比较。
他返回一个整数来表示比较结果,返回值意义如下:0:相等 1:大于 -1:小于 (A的ASCII码是65,a的ASCII码是97)
string A("aBcd"); |
4.string的插入
- push_back() : 在尾部插入字符
- insert(pos, char) : 在 pos前插入字符串char
5.string字符串拼接
- append() : 尾部加入字符串
- : 尾部加入字符串
// 方法一:append() |
6.string遍历
迭代器 或者 下标法
string::iterator iter = s1.begin(); |
str[i] / str.at(i);
7.string的删除
- iterator erase(iterator p);//删除字符串中p所指的字符
- iterator erase(iterator first, iterator last);//删除字符串中迭代器区间
[first,last)上所有字符 左闭右开 - string& erase(size_t pos = 0, size_t len = npos);//删除字符串中从索引位置pos开始的len个字符
- void clear();//删除字符串中所有字符
string s1 = "123456789"; |
8.string的字符替换
- string& replace(size_t pos, size_t n, const char *s);//将当前字符串从pos索引开始的n个字符,替换成字符串s
- string& replace(size_t pos, size_t n, size_t n1, char c); //将当前字符串从pos索引开始的n个字符,替换成n1个字符c
- string& replace(iterator i1, iterator i2, const char* s);//将当前字符串
[i1,i2)区间中的字符串替换为字符串s
string s1("hello,world!"); |
9.string大小写替换
- tolower(); toupper();
string s = "ABCDEFG"; |
- STL的transform算法配合的toupper和tolower
string s = "ABCDEFG"; |
10.string的查找
size_t find (constchar* s, size_t pos = 0) const;
//在当前字符串的pos索引位置开始,查找子串s,返回找到的位置索引,-1表示查找不到子串
//在当前字符串的pos索引位置开始,查找子串s,返回找到的位置索引,-1表示查找不到子串//在当前字符串的pos索引位置开始,查找子串s,返回找到的位置索引,-1表示查找不到子串
size_t find (charc, size_t pos = 0) const;
//在当前字符串的pos索引位置开始,查找字符c,返回找到的位置索引,-1表示查找不到字符
//在当前字符串的pos索引位置开始,查找字符c,返回找到的位置索引,-1表示查找不到字符//在当前字符串的pos索引位置开始,查找字符c,返回找到的位置索引,-1表示查找不到字符
size_t rfind (constchar* s, size_t pos = npos) const;
//在当前字符串的pos索引位置开始,反向查找子串s,返回找到的位置索引,-1表示查找不到子串
//在当前字符串的pos索引位置开始,反向查找子串s,返回找到的位置索引,-1表示查找不到子串//在当前字符串的pos索引位置开始,反向查找子串s,返回找到的位置索引,-1表示查找不到子串
size_t rfind (charc, size_t pos = npos) const;
//在当前字符串的pos索引位置开始,反向查找字符c,返回找到的位置索引,-1表示查找不到字符
//在当前字符串的pos索引位置开始,反向查找字符c,返回找到的位置索引,-1表示查找不到字符//在当前字符串的pos索引位置开始,反向查找字符c,返回找到的位置索引,-1表示查找不到字符
size_tfind_first_of (const char* s, size_t pos = 0) const;
//在当前字符串的pos索引位置开始,查找子串s的字符,返回找到的位置索引,-1表示查找不到字符
//在当前字符串的pos索引位置开始,查找子串s的字符,返回找到的位置索引,-1表示查找不到字符//在当前字符串的pos索引位置开始,查找子串s的字符,返回找到的位置索引,-1表示查找不到字符
size_tfind_first_not_of (const char* s, size_t pos = 0) const;
//在当前字符串的pos索引位置开始,查找第一个不位于子串s的字符,返回找到的位置索引,-1表示查找不到字符
//在当前字符串的pos索引位置开始,查找第一个不位于子串s的字符,返回找到的位置索引,-1表示查找不到字符//在当前字符串的pos索引位置开始,查找第一个不位于子串s的字符,返回找到的位置索引,-1表示查找不到字符
size_t find_last_of(const char* s, size_t pos = npos) const;
//在当前字符串的pos索引位置开始,查找最后一个位于子串s的字符,返回找到的位置索引,-1表示查找不到字符
//在当前字符串的pos索引位置开始,查找最后一个位于子串s的字符,返回找到的位置索引,-1表示查找不到字符//在当前字符串的pos索引位置开始,查找最后一个位于子串s的字符,返回找到的位置索引,-1表示查找不到字符
size_tfind_last_not_of (const char* s, size_t pos = npos) const;
//在当前字符串的pos索引位置开始,查找最后一个不位于子串s的字符,返回找到的位置索引,-1表示查找不到子串
string s("dog bird chicken bird cat"); |
11.string的排序 sort(s.begin(),s.end())
string s = "cdefba"; |
12.string的分割/截取 strtok() / substr()
- strtok()
char str[] = "I,am,a,student; hello world!"; |
- substr()
string s1("0123456789"); |
13. string char 转换
将数值 val 转换为 string 。val 可以是任何算术类型(int、浮点型等)。string s = to_string(val)
14. string reverse反转
reverse(str.begin(),str.end());
reverse还可用于数组 reverse(array, array+arrat.lenth())
STL任何容器都可以。
sscanf(const char *str, const char *format, …);
int converted = sscanf("20191103", "%04d%02d%02d", &year, &month, &day); |
string 转 time_t
//linux下存储时间常见的有两种存储方式,一个是从1970年到现在经过了多少秒,一个是用一个结构来分别存储年月日时分秒的。 |
常用函数
time_t time(time_t *t); //取得从1970年1月1日至今的秒数 |
算法
贪心
背包问题
01背包,物品最多只能放一次
int dp[m+1][n+1] = {0};
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(j>=w[i]) dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]); //从上一行找
else dp[i][j] = dp[i-1][j];
}
}降为一维数组
int dp[n+1] = {0};
for(int i=1;i<=m;i++){
for(int j=n;j>=w[i];j--){ // 从后向前便利,只会计算一次
dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
}
}完全背包问题,物品可重复放
# 二维
int dp[m+1][n+1] = {0};
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(j>=w[i])
dp[i][j] = max(dp[i-1][j], dp[i][j-w[i]] + v[i]); // 与01区别是从当前行找
else
dp[i][j] = dp[i-1][j]
}
}
# 一维
int dp[n_1] = {0};
for(int i=1;i<=m;i++){
for(int j=w[i];j<=n;j++){ // 从前向后便利,多次计算之前的
dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
}
}
