C++ 标准库学习
有一些书 《c++11 FAQ 中文版》 https://wizardforcel.gitbooks.io/cpp-11-faq/content/88.html
c++ 手册 https://zh.cppreference.com/w/cpp
c++ 手册 https://www.runoob.com/cplusplus/cpp-tutorial.html
微软 c++ 手册 https://docs.microsoft.com/zh-cn/cpp/cpp/c-cpp-language-and-standard-libraries?view=vs-2019
int main()
{
std::cout << "Hello World!" << std::endl;
}
常用
通用
using namespace std; //引用所有空间
using std::cout; // 只引用需要的元素, 这样安全一些
// 常用
/// 输出
std::cout << "Hello World!" << std::endl;
/// 输入
std::cout << "请输入两组值" << std::endl;
int value;
std::string name;
std::cin >> value >> name; // 输入, 空格分隔多个输入
std::cout << value << ' ' << name << std::endl;
/// 变量的长度
long long longValue = 0;
std::cout << "变量长度 " << sizeof(value) << std::endl;
std::cout << "int 长度 " << sizeof(int) << std::endl;
/// 自动变量类型, 根据变量自动推断类型
auto autoValue = 0L;
std::cout << autoValue << std::endl;
/// 类型定义
typedef long long MyLongType; // 自定义一种类型
/// 常量
const auto pi = 3.14; // 定义不变的值
constexpr double CountPi() { return 3.14 / 7; }
CountPi(); // 调用一个常数表达式, 在编译时计算结果嵌入
/// 枚举 建议使用 enum class
enum class WorkStatus { None = 25, Running, Busy, Finished };
auto status = WorkStatus::Running; // 编译器自动编译于int
// #define 定义常量
#define PI 3.14 // 会被字符替换到代码中, 现在不推荐使用
数组
// 常用 ,定义一个 15 个值的数组,初始值为12
const auto myNumLen = 5;
int my_nums [myNumLen] = { 12, 11, 10, 9, 8};
std::cout << "array size" << sizeof(int) * myNumLen << std::endl;
int my_num_nums[3][5] = { 0 };
my_num_nums[1][3] = 5;
// 动态数组
std::vector<int> ls(3);
ls[0] = 15;
ls.push_back(23);
字符串
char strArray[] = { 'h', 'e', 'l' , 'l' , 'o', '\0' }; // 不建议使用
std::cout << strArray << std::endl;
std::string str;
str.push_back('h');
str.append("ello");
std::cout << str << std::endl;
函数
// 使用默认参数
void Func(std::string name, int count = 13 /* 默认参数值 */ ) { }
// 传入数组
void Func(int numbers [], int length) { }
// 传入引用参数, 函数内部可以改变值
void CountArea(double& value) { }
// 内联参数,代码将会展开至调用处
inline long GetPi() { return 10; }
// auto 自动推断返回值类型
auto GetPiEx(){ return 1.35; }
// lambda
std::vector<int> nums({1, 2, 3, 4});
std::sort(nums.begin(), nums.end(), [] (const int n1, const int n2) { return n1 < n2; });
指针与引用
// 声明一个指向 null 的指针
int* pint = nullptr;
// 使用 & 取出变量指针地址 将其赋值给一个指针
auto value = 32;
pint = &value;
// 使用变量地址初始化一个指针
long long longValue = 32;
auto* pLong = &longValue;
// 使用 * 指向指针的值进行操作,
*pLong = 64;
std::cout << "指针地址 " << std::hex << pLong << std::endl;
std::cout << "指针对应值 " << *pLong << std::endl;
std::cout << "指针大小 " << sizeof(pLong) << std::endl; // 指针大小是一致的,与指针对应的值无关
std::cout << "值大小 " << sizeof(*pLong) << std::endl;
动态指针
auto* pInt = new int; // 分配一个int值的空间
auto* pIntAry = new int[32]; // 分配一个 int 数组的空间
auto* firstAddress = pIntAry; // 转为我会移动 pIntAry 地址,所以将初始地址保存一下
pIntAry[0] = 1;
pIntAry[1] = 2;
pIntAry[2] = 3;
// ++ -- 移到数组的上一下或是下一个地址
std::cout << "当前值的地址 " << std::hex << pIntAry << std::endl;
std::cout << "当前数值 " << *pIntAry << std::endl;
pIntAry++;// 移至下一个地址
std::cout << "地址大小 " << sizeof(pIntAry) << std::endl;
std::cout << "下一值的地址 " << std::hex << pIntAry << std::endl;
std::cout << "下一数值 " << *pIntAry << std::endl;
// new 出的地址必须 delete , 数组必须 delete []
delete pInt;
delete[] firstAddress;
指针使用 const
// 地址为常量无法修改,但是地址对应的值可以修改
int* const pValue = &value;
pValue = 32; // 错误
*pValue = 32; // 正确
// 地址可以改为指向其它值,但是地址对应的值无法修改
const int* pValue = &value;
pValue = &otherValue; // 正确
*pValue = 32; // 错误
// 地址及地址对应的值都不能修改
const int* const pValue = &value;
pValue = otherValue; // 错误
*pValue = &otherValue; // 错误
// 函数
void Func(
int* pvalue1 /* 可修改地址及对应值, 修改地址无意义,因为修改后无法传出 */
, int* const pvalue2 /* 不可修改地址及可修改对应值 */
, const int* pvalue3 /* 可修改地址及不可修改对应值 */
, const int* const pvalue4 /* 不可修改地址及对应值 */
) { }
一般建议
-
初始化时或是 delete 后将其设置为 nullptr
-
分配失败检测
``` c++ // 1. 使用异常 try { pValue = new(std::nothrow) int[0x1fffff]; // 分配成功 } catch (std::bad_alloc) { // 分配失败 }
// 2. 使用 new(nothrow)
int* pValue = new(std::nothrow) int[0x1fffff];
if(pValue != nullptr)
{
// 分配成功
}
```
引用 &
引用是对象的别名, 指向同一个对象地址。
```c++ int value1 = 32; int& value2 = value1; // 两者地址相关
std::cout << std::oct << &value1 << std::endl;
std::cout << std::oct << &value2 << std::endl;
```
#### 引用作为函数参数
函数传值时会复制值。对于大对象如果使用别名传入就可以免去复制值部分
``` c++ void Func(int& value) { // 相当于本地变量使用 value = 64; }
int value = 32;
std::cout << value << std::endl;
Func(value);
std::cout << value << std::endl;
void Func(int& value)
{
// 相当于本地变量使用
value = 64;
}
int value = 32;
std::cout << value << std::endl;
Func(value);
std::cout << value << std::endl;
// 如果要快捷传入值,但是不能修改值,要使用 const void Func(const int& value) {} ```
对象
常用
class Human
{
private:
int age;
std::string name;
public:
// 默认构造
explicit Human() { this->age = 32; }
// 使用参数构造
explicit Human(std::string name, int age = 64)
{
this->name = std::move(name); // 相当于说明 name 以后的东西交给 this->name 来处理,name 不要再使用了
this->age = age;
}
// 带初始化属性构造
explicit Human(std::string name) : age(64)
{
this->name = std::move(name);
}
// 析构函数
~Human() { }
int GetAge() const { return this->age; }
std::string GetName() const { return this->name; }
};
explicit
声明构造函数不能用隐式转式, 似乎建议这么操作
// 如果有构造函数
Human(std::string name, int age = 32) {}
Human human = std::string("x"); // 因为 age 有默认值,所以会调用 Human(std::string name, int age = 32) 构造
// 如果使用 explicit
explicit Human(std::string name, int age = 32) {}
Human human = std::string("x"); // 错误。强制不能隐式转换
Human human(std::string("x")); // 只能显示指定构造函数初始化
静态函数与静态属性
https://blog.csdn.net/Chroniccandy/article/details/108621102
class Pet
{
private:
static int count ; //类内声明一个static类的静态数据成员, static int count=10;//类内声明静态变量 错误 声明时不可进行赋值操作(此时未分配内存 变量赋值需要分配内存)
static const int count = 0;//🌟静态常量成员可以在类内初始化
const int count ; // const数据成员不能在类内定义时初始化,在类外初始化也不行,其通常通过构造函数初始化.
private:
static Pet A;//todo静态数据成员的类型,可以是本类的类型,而普通数据成员则不可以
Pet A;//错误,普通数据成员的类型不能是本类的类型
// 🌟注意: 指针和引用数据成员的类型,也可以是本类的类型,而普通数据成员则不可以
Pet * a;//正确
Pet& b;//正确
public:
static void B(); //建立一个静态成员函数
};
A::B();//正确访问静态成员函数的方式 格式为classname::funcname();
int Pet::count = 0;类外定义并初始化静态数据成员 (要使用静态变量和静态成员函数 必须分配内存和初始化)
c++&& 移动构造
构造函数中使用 && 防止重复深拷贝 https://www.jianshu.com/p/cb82c8b72c6b https://www.cnblogs.com/zpcdbky/p/5275959.html
其它
//防止类对象复制
class President
{
private:
President(const President&); // 禁止复制
President& operator= (const President); // 禁止赋值
};
// 唯一实例
class President
{
private:
President(); // 禁止构造
President& operator= (const President); // 禁止复制
public:
static President& GetInstance() // 返回唯一实例
{
static President oneInstance;
return oneInstance;
}
};
// 禁止栈中创建对象, President president 创建对象。比如构造时会占用大内存,而栈空间大小一般都有限
class President
{
private:
~President(); // 禁止释放
};
// 隐式转换, 可用 explicit 来禁止转换
class Presiden{
Presiden(const char* const name){} // 可以 Presiden presiden = "xxxx"; 自动转换
}
// sizeof
// 类声明中所有数据属性占用的总内存
结构 struct
与类相同,类型默认公有
友元
class Human
{
private:
friend void Display(const Human& human);// Display 函数可以访问 Human 私有对象
friend class President; // President 类可以访问 Human 私有对象
}
共用体
union 类型名 { Type1 member1; Type2 member2 } // 不常用,先不记录
聚合初始化
// 按内存分布进行初始化
Human human {8, {'h', 'e', 'l', 'l', '0'}, 34.5} // 根据类的属性定义进行初始化
类和对象使用 constexpr
类构造函数及函数或对象上可以使用constexpr , 编译器会尽量进行优化
类
class Fish
{
private:
bool isfishWaterFish;
public:
Fish() : isfishWaterFish(true) { }
void Swim() {}
};
// final 禁止继承
class Tuna final: public Fish
{
public:
void Swim() // 注意,如果重载了同名函数,会隐藏父函数,可以使用 using Fish::Swim 声明可调用父函数
{
Fish::Swim(); // 调用基类
}
};
Fish fish;
fish.Fish::Swim(); // 调用基数方法
公有继承、私有继承
一般不用
多态
class Fish
{
private:
bool isfishWaterFish;
public:
Fish() : isfishWaterFish(true) { }
// 纯虚函数
virtual void Fly() {};
// 纯虚函数, 类不能实例化
virtual void Swim() = 0;
};
class Tuna: public Fish
{
public:
// 禁止该函数重载
void Swim() override final
{
Fish::Swim(); // 调用基类
}
};
操作符重载
类型转换
class Fish {};
class Dog : public Fish { };
Dog dog;
// static_cast 编译期间对类型转换进行判断
auto pfish = static_cast<Fish*>(&dog);
auto pdog = static_cast<Dog*>(pfish);
// dynamic_cast 运行时动态判断,如果不能转换返回 nullptr
auto* const pfish = dynamic_cast<Fish*>(&dog);
if(pfish == nullptr)
{
// 转换失败作
}
// reinterpret_cast 与传统方式一样,不管类型,强制转
auto* pfish = reinterpret_cast<Fish*>(&dog);
// const_cast 去掉对变量中的 const 限制
// 用到再说
宏与模板类
宏
应该不再建议
// 字符替换
#define COUNT(X) ((X)*(X))
#define PI 3.1416
// 头文件中引用一次
#ifndef MYSTRING
#define MYSTRING
#include <string>
// 调用
#endif
// 验证表达式
#include <assert.h>
assert(true)
模板类
看起来是在编译时进行类型验证, 如果在模板函数或类中对模板对象调用了不正确的函数或方法会编译错误
模板函数
// 模板函数
template <typename T1>
bool GetCountFunc(const T1& value)
{
std::cout << strlen(value) << std::endl;
return true;
}
GetCountFunc<const char*>("12345678");
GetCountFunc("12345678");
GetCountFunc(34); // 错误,因为在模板函数中 strlen(34) 非法
模板类
// 模板类在使用时才进行编译,不然会被忽略
// T1 指定了默认模板类型,myclass<> 则使用默认模板类型
// T2 如果没有指定 ,则使用T1 的类型
template <typename T1 = int, typename T2 = T1>
class myclass
{
private:
T1 value;
public:
void SetValue(const T1& v) { this->value = v; }
T1& GetValue() { return this->value; }
// 静态属性
static T1 SaticValue;
bool Compare(const T1& v)
{
return this->value == v;
}
};
// 对模板类静态属性进行初始化
template <typename T1, typename T2> T1 myclass<T1, T2>::SaticValue;
// 对 char, char 使用静态属性
myclass<char, char>::SaticValue = 'a';
std::cout << myclass<char, char>::SaticValue << std::endl;
// 对 int, int 没有设置
std::cout << myclass<int, int>::SaticValue << std::endl;
元组
可以与可变参数进行配合
https://www.cnblogs.com/qicosmos/p/4325949.html
https://wizardforcel.gitbooks.io/cpp-11-faq/content/14.html
编译时检查
static_assert
操作符重载
有空再看
STL 标准库
一分钟介绍 https://www.cnblogs.com/findumars/p/6760029.html
微软 c++ 标准库 https://docs.microsoft.com/zh-cn/cpp/standard-library/cpp-standard-library-reference?view=vs-2019
其它 stl 标准库 https://www.runoob.com/cplusplus/cpp-standard-library.html
函数对象
相当于回调接口,可以使用函数,也可以使用对象,但是对象的函数定死了就是 operator , 需要支持范型, 在各个 stl 标准库需要回调操作的地方提供
// 函数的方式实现
template<typename elementType>
void FuncDisplayelenet(const elementType& element)
{
std::cout << "func " << element << std::endl;
}
// 类方式实现
template<typename elementType>
struct Displayelenet
{
void operator () (const elementType& element) const
{
std::cout << "class " << element << std::endl;
}
};
c
// 以函数方式实现
std::for_each(nums.cbegin(), nums.cend(), FuncDisplayelenet<int>);
// 以类方式实现
std::for_each(nums.cbegin(), nums.cend(), Displayelenet<int>());
LAMBDA
// lambda代替函数对象作为回调传入
// [ 传入值 ] (参数定义) { 函数体 };
const std::vector<int> nums{ 1, 2, 3, 4, 5, 6, 7 };
std::for_each(nums.cbegin(), nums.cend(), [](int element) { std::cout << element << std::endl; });
智能指针
大致上是一个范型的指针包装器,因为在堆栈中创建,超出使用范围后自动析构, 在析构函数中释放指针
https://www.cnblogs.com/lanxuezaipiao/p/4132096.html
https://juejin.im/post/6844903993055920141
// auto_ptr
// 早期的智能指针,如果可能会多个对象同时指向同一个指针,导致多次释放而造成的异常
// 而在已经不再使用
// unique_ptr
// 建议使用 make_unique<T>(args) 创建
// 这是个独占式的指针对象,在任何时间、资源只能被一个指针占有,当unique_ptr离开作用域,指针所包含的内容会被释放
// 禁止释放,复制,独占性使用
// shared_ptr , 看起来是线程安全的
// 建议使用 make_shared<T>(args) 创建
// 拥有共享对象所有权语义的智能指针. 内部有引用计数器
// 如果需要对同一个指针多个引用的话,使用该智能指针
// 新的智能指针的好处
// 1. 函数返回时
std::unique_ptr<std::string> demo(const char* s)
{
std::unique_ptr<std::string> temp(new std::string(s));
return temp;
}
// 调用 dmeo 时,内部的 temp 传出时赋值给 ps,temp 会被释放,但是会智能判断不会对引用对象进行释放
const auto ps = demo("test");
auto len = ps->length();
// 2. 赋值时
const std::unique_ptr<std::string> pu1(new std::string("hello world"));
// 赋值后因为 pu1 还会被继续使用。这样会两个对象指向同一个指针。 会编译错误
auto pu2 = pu1; // #1 not allowed
std::unique_ptr<std::string> pu3;
// 赋值时是一个临时对象,临时智能指针不会释放,允许
pu3 = std::unique_ptr<std::string>(new std::string("You")); // #2 allowed
使用原则
如何选择智能指针?
在掌握了这几种智能指针后,大家可能会想另一个问题:在实际应用中,应使用哪种智能指针呢? 下面给出几个使用指南。
(1)如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:
- 有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;
- 两个对象包含都指向第三个对象的指针;
- STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。
(2)如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。如果函数使用new分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器在那个,只要不调用将一个unique_ptr复制或赋给另一个算法(如sort())。
流
提供一个统一的模式对数据(文件、控制台、设备)进行读写
https://murphypei.github.io/blog/2019/01/cpp-custom-iostream
std::cout // 标准输出
std::cin // 标准输入
std::err // 错误信息
fstream // 文件流基类
ofstream
ifstream
stringstream // 操作字符串流
设置输出格式
http://c.biancheng.net/view/275.html
https://blog.csdn.net/bagboy_taobao_com/article/details/44652245
using namespace std;
int n = 141;
//1) 分别以十六进制、十进制、八进制先后输出 n
cout << "1)" << hex << n << " " << dec << n << " " << oct << n << endl;
double x = 1234567.89, y = 12.34567;
//2)保留5位有效数字
cout << "2)" << setprecision(5) << x << " " << y << " " << endl;
//3)保留小数点后面5位
cout << "3)" << fixed << setprecision(5) << x << " " << y << endl;
//4)科学计数法输出,且保留小数点后面5位
cout << "4)" << scientific << setprecision(5) << x << " " << y << endl;
//5)非负数显示正号,输出宽度为12字符,宽度不足则用 * 填补
cout << "5)" << showpos << fixed << setw(12) << setfill('*') << 12.1 << endl;
//6)非负数不显示正号,输出宽度为12字符,宽度不足则右边用填充字符填充
cout << "6)" << noshowpos << setw(12) << left << 12.1 << endl;
//7)输出宽度为 12 字符,宽度不足则左边用填充字符填充
cout << "7)" << setw(12) << right << 12.1 << endl;
//8)宽度不足时,负号和数值分列左右,中间用填充字符填充
cout << "8)" << setw(12) << internal << -12.1 << endl;
cout << "9)" << 12.1 << endl;
输入流
using namespace std;
// 读出单个字符
char c = cin.get();
// 读出一批字符
char buffer[10];
cin >> buffer; // 应该不安全,输入多时,会溢出
cin.get(buffer, 9); // 使用这个方法
// 当输入有空格时后半部分会收不到, 使用 getline
string name;
getline(cin, name);
cout << "'" << name << "'" << endl;
stringstream
可以将各种格式与字符串互转
std::stringstream str;
异常
try
{
throw object; // 引发异常
}
catch (...) // 处理所有异常
{
}
常用异常
// c++ 标准库基类 std::exception
// what 函数可以返回原因
线程
// 线程示例
void ThreadWorkFunc(int idx)
{
auto id = std::this_thread::get_id();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::cout << idx << std::endl;
}
// 创建一个线程对象, 并传入初始化参数
// thread 类禁用了 赋值 拷贝
// thread 析构时调用线程的 terminate ,将其强制释放
std::thread thread(ThreadWorkFunc, 32);
// 调用 join 阻塞当前有线程,直到 thread 完成。 一般线程在 join 前都是 joinabl 的
// 调用 join 后
{
if (thread.joinable())
{
thread.join();
}
}
{
// 将线程与 thread 对象分离, thread 释放不再管理真实线程对象
thread.detach();
}
// 当前线程 id
auto id = thread.get_id();
// 将线程的管理权交给 otherThread, thread 不再控制线程
std::thread otherThread(std::move(thread));
// 互换 两个 thread 对线程的控制权
otherThread.swap(thread);
// 强制中止当前线程
std::terminate();
// 获取当前线程 id
std::this_thread::get_id(); // 另外一种获取 auto id = thread.get_id();
// 返回原始的线程句柄
thread.native_handle();
// 检测系统的进发性能
std::thread::hardware_concurrency();
// 在线程中调用,使当前线程放弃 cpu 时间
std::this_thread::yield();
// 暂停当前线程直到到指定的时间 https://stackoverflow.com/questions/17565754/inputs-to-sleep-until
tm timeout_tm = { 0 };
// set timeout_tm to 14:00:01 today
timeout_tm.tm_year = 2013 - 1900;
timeout_tm.tm_mon = 7 - 1;
timeout_tm.tm_mday = 10;
timeout_tm.tm_hour = 14;
timeout_tm.tm_min = 0;
timeout_tm.tm_sec = 1;
timeout_tm.tm_isdst = -1;
const auto timeout_time_t = mktime(&timeout_tm);
const auto timeout_tp = std::chrono::system_clock::from_time_t(timeout_time_t);
std::this_thread::sleep_until(timeout_tp);
// 暂停当前线程直到过去指定的时间
const std::chrono::milliseconds dura(2000);
std::this_thread::sleep_for(dura);
其它
时间
https://paul.pub/cpp-date-time/