long long & unsigned long long
- long long 超长整型是 C++ 11 标准新添加的
- 对于有符号 long long 整形,后缀用 “LL” 或者 “ll” 标识。例如:
long long a = 10ll; - 对于无符号 long long 整形,后缀用 “ULL”、“ull”、“Ull” 或者 “uLL” 标识。例如:
unsigned long long b = 10ull; - 在
<climits> 头文件中的LLONG_MIN、LLONG_MAX 和 ULLONG_MIN 分别代表了当前平台下最小的longlong值、最大的longlong值和最大的unsigned longlong值
初始化列表
class test
{
public:
test(int x){}
}
struct test1
{
int x;
int y;
}
test1 func()
{
return { 1, 2 };
}
int main()
{
test x(10);
test y = { 20 };
test z{ 30 };
int arr[] = { 1, 2, 3, 4, 5 };
int* p = new int[6]{ 0, 1, 2, 3, 4, 5 };
}
nullptr类型
- NULL宏定义来自于C语言,用于给予指针变量初始值,但在C++中,NULL带来了二义性,因为C++允许函数重载,例如:
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
void func(int a){}
void func(int* a){}
这时候我们调用func(NULL),编译器会认为我们要调用的是int类型的func,如果我们要调的是指针类型的怎么办,这个时候加入的nullptr作用就来了
- nullptr可以明确的区分指针类型,虽然它也有二义性,但它类型不会错,只需指定一种指针类型即可,例如:
void func(int a){}
void func(int* a){}
void func(double* a){}
func(nullptr)
func((int*)nullptr)
auto智能推导类型
- auto 关键字会在编译期间自动推导出变量的类型,使得某些较长的类型无需再手动定义,编写代码时会相当的方便
- 定义:
auto a = value; a为变量名,value为变量初始值 - 简意:auto关键字仅为占位符,在编译期间会被替换为真正的类型,如同define一样
- 简单例子:
auto a = 10; auto b = 10.5; auto c = &b; auto d = "www.csdn.com"; - 解释:
a的类型被推导为int类型,10为整型 b的类型被推导为double类型,10.5为小数,auto默认为double,使用类型为float,需改为10.5f c的类型被推导为double类型,&b为变量b的地址 d的类型被推导为const char类型,"www.csdn.com"为字符串 - 注意:
auto关键字在类型推导时不可产生二义性,例如:auto a = 10, b = 10.5; 在推导引用类型时需加入&符号,例如:auto& a = b; auto在定义时必须初始化,不能作为形参在函数中使用,不能作为模板参数使用 不能用于定义数组,但可接收数组变量退化为指针,例如:char arr[] = "www.csdn.com"; auto arr1 = arr; ,此时的arr1就会被推导为char*指针
decltype关键字
- decltype是 C++11 新增的一个关键字,它和 auto 的功能一样,都用来在编译时期进行自动类型推导,那为什么有了auto还要引入decltype关键字呢,因为auto并不能适用于所有场景,所以引入了decltype
- 定义:
decltype(type) a = value; a为变量名,value为变量初始值,type可以是变量,也可以是表达式 - 简意:type就是一个表达式,必须产出一个结果并且不能为void类型
- 差异:和auto的差异是auto必须在定义时初始化,而decltype不需要,因为auto是靠右值推导类型,而decltype是靠type
- 定义:
int a = 1; decltype(a) b;//此时的b是int类型,在这里的b没有初始化 decltype(10.5) c = 20.8;//此时的c是double类型 decltype(c + 100) d = 30.55;//此时的d是double类型 - 注意:
decltype定义引用类型时需加双层括号,例如:decltype( (d) ) e = d; 此时加了双层括号,推导的类型肯定是引用类型,而单层括号时需要推导的变量为引用类型,定义的变量才会是引用
for循环
for(type : list)
{
}
- type是定义的变量,可以使用auto类型推导定义,list就是范围的列表或者容器,例如:
int arr[] = { 1, 2, 3, 4, 5 };
vector<type> v;
for(auto& i : v)
{
}
- 除此之外,新语法格式的 for 循环还支持遍历用{ }大括号初始化的列表,例如:
for(int& i : { 1, 2, 3, 4, 5 })
{
std::cout << i;
}
using定义别名
- using的加入是为了替代typedef,因为typedef在某些情况会有局限性,而using拥有typedef特性的同时还扩展了一些它做不到的事,例如:
template <typename t>
using str_map = std::map<string, t>;
str_map<int> a;
template <typename t>
struct strmap
{
std::map<string, t> str_map;
}
strmap<int>::str_map b;
缺省模版参数
- 在C++11中,模板可以指定某个类型为默认类型,例如:
template <typename R = int, typename val>
R func(val a)
{
return a;
}
func(10);
func<char>(10);
func<double, int>(10);
- 在上述代码中,出现了一个类型都没有指定的调用,是因为编译器自动推导了第二个参数的类型,这意味着,当默认模板参数和编译器自行推导出模板参数类型的能力一起结合使用时,代码的书写将变得异常灵活。
override与final
- override的作用是检查某函数是否重写了基类的函数,如果没有编译报错
- final的作用是表示该虚函数不能再继续继承下去
右值引用&std::move
- 什么是左值什么是右值?
int a = 10; a就是左值,10就是右值 有名称的、可以获取到存储地址的表达式即为左值;反之则是右值。 而我们再创建个变量 int& b = a; 这里的b就是左值引用 - 而右值引用在C++11中也进行了实现:&&
int&& a = 10; 这就是右值引用,可以通过a修改10在内存里的数字 - std::move 唯一的功能就是将一个左值强制转化为右值引用,通过右值引用使用该值,实现移动语义。
- 例子:
class string
{
......
public:
string(string&& b) : _str(b._str)
{
b._str == nullptr;
}
......
}
string s1("test");
string s2(std::move(s1));
string s3(s2);
- s2调用移动构造函数创建,s3调用拷贝构造函数创建
lambda表达式
- 定义:
[捕获列表](参数)->返回值{ 函数体 }; - 简意:lambda表达式实际上是一个匿名类函数,在编译时会将表达式转换为匿名类函数
- 例子:
void print(int b) == [](int b) {}
int add(int a, int b) == [](int a, int b){ return a + b; }
int a = 10;
auto fn = [=]{ a = 20; }
auto fn = [&]{ a = 20; }
auto fn = [&a]{ a = 20; }
class test
{
public:
test(){}
int add(int& a, int& b) { return a + b; }
}
test t;
auto fn_add = std::bind(&test::add, &t, std::placeholders::_1, std::placeholders::_2);
auto ret = fn_add(10, 20);
int a = 10;
auto fn = [&a](int b){ a = a * b; return a; }
class lam
{
public:
lam(int& a) : _a(a)
{
}
public:
auto operator()(const int a)
{
_a = _a * a;
return _a;
}
private:
int& _a;
};
lam l(a);
l(10) == fn(10)
- lambda表达式的出现也解决了很多地方的函数指针调用问题,例如:
int arr[] = { 2, 5, 3 ,4 ,9 ,0 ,7 ,8, 1, 6 };
bool compare(int& a, int& b){ return a > b; }
std::sort(arr, arr + 10, compare);
std::sort(arr, arr + 10, [](int& a, int& b){ return a > b; });
- 注意:
由于这是个匿名类,在使用lambda表达式时不可在函数体中申请内存,否则会造成意想不到的错误。
thread
- C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的线程,必须包含< thread >头文件,该头文件声明了 std::thread 线程类
- 简单定义:
int add(int a, int b){ return a + b; }
std::thread td(add, 10, 20);
td.join();
std::thread td([&](int a, int b){return a + b;}, 10, 20);
td.join();
- 线程结束后我们应该怎么去回收线程资源呢?thread线程库给了我们2种选择
- join:当新的线程终止时,join()会清理相关的资源,然后返回,调用线程再继续向下执行。也就是阻塞式线程
- detach:会从调用线程中分离出新的线程,之后不能再与新线程交互。非阻塞式线程
- 原子类型:atomic_xx,类似于锁,多线程访问时不会同时访问一个数据,仅适用于基础类型和基础类型的扩展类型
- 还有其他标准库多线程锁(condition_variable),详情请看C++25设计模式之线程池模式
智能指针
auto_ptr
- 在c++11之后,它被弃用了,原因就是因为当把一个auto_ptr对象赋值给另一个变量时, 原有的对象的指针就为空了. 我们很容易实现它:
template <typename T>
class AutoPtr
{
private:
T* _ptr;
public:
AutoPtr(T* p = nullptr) : _ptr(p)
{
}
~AutoPtr()
{
delete _ptr;
_ptr = nullptr;
}
AutoPtr(AutoPtr<T>& other) : _ptr(other._ptr)
{
other._ptr = nullptr;
}
AutoPtr<T>& operator=(AutoPtr<T>& other)
{
if (this != &other)
{
if(_ptr)
{
delete _ptr;
}
_ptr = other._ptr;
other._ptr = nullptr;
}
return *this;
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
void reset(T* p) { if(_ptr){ delete _ptr; } _ptr = p; }
};
unique_ptr
- unique_ptr与auto_ptr基本相似,有一点不一样: unique_ptr很聪明,它不允许拷贝构造也不允许赋值操作,只允许移动构造和移动赋值操作. 所以实现它也很容易:
template <typename T>
class UniquePtr
{
private:
T* _ptr;
public:
UniquePtr(T* p = nullptr) : _ptr(p)
{
}
~UniquePtr()
{
delete _ptr;
_ptr = nullptr;
}
UniquePtr(UniquePtr<T>& other) = delete;
UniquePtr<T>& operator=(UniquePtr<T>& other) = delete;
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
void reset(T* p) { if(_ptr){ delete _ptr; } _ptr = p; }
};
shared_ptr
- 它允许多个shared_ptr对象共享一块内存, 与前两者有所不同。本质是就是维护一个内存块的引用计数,引用计数什么时候变为0了,什么时候释放内存。简单实现:
template <typename T>
class SharedPtr
{
private:
size_t* _count;
T* _ptr;
public:
SharedPtr(T* p = nullptr) : _ptr(p)
{
if(_ptr)
{
_count = new size_t{1};
}
else
{
_count = new size_t{0};
}
}
~SharedPtr()
{
(*_count)--;
releasePtr();
}
SharedPtr(const SharedPtr& other)
{
if (this != &other)
{
*(_count)--;
releasePtr();
_ptr = other._ptr;
_count = other._count;
(*_count)++;
}
}
SharedPtr<T>& operator=(const SharedPtr& other)
{
if(_ptr != other._ptr)
{
if (_ptr)
{
(*_count)--;
}
releasePtr();
_ptr = other._ptr;
_count = other._count;
(*_count)++;
}
return *this;
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
auto reset(T* p)
{
if (_ptr != p)
{
(*_count)--;
releasePtr();
}
}
size_t use_count()
{
return *_count;
}
T* get()
{
return _ptr;
}
private:
void releasePtr()
{
if (*_count == 0)
{
delete _count;
delete _ptr;
}
_count = nullptr;
_ptr = nullptr;
}
};
- 使用shared_ptr时需注意避免循环引用,例如:
#include <memory>
using namespace std;
struct ListNode
{
int _data;
shared_ptr<ListNode> _prev;
shared_ptr<ListNode> _next;
~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
return 0;
}
-
node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。 -
node1的_next指向node2,node2的_prev指向node1,引用计数变成2。 -
node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。 -
也就是说_next析构了,node2就释放了。 -
也就是说_prev析构了,node1就释放了。 -
但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。
那么有没有解决方案呢? 答案是有的
- weak_ptr指针是为了shared_ptr存在的,它的作用就是不增加引用计数,还是上面的例子:
#include <memory>
using namespace std;
struct ListNode
{
int _data;
weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next;
~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
return 0;
}
- 解决了引用计数问题,那么我是malloc的内存空间,shared_ptr怎么释放呢? 答案是删除器概念
#include <memory>
using namespace std;
template<class T>
struct FreeFunc
{
void operator()(T* ptr)
{
cout << "free:" << ptr << endl;
free(ptr);
}
};
template<class T>
struct DeleteArrayFunc
{
void operator()(T* ptr)
{
cout << "delete[]" << ptr << endl;
delete[] ptr;
}
};
int main()
{
FreeFunc<int> freeFunc;
shared_ptr<int> sp1((int*)malloc(4), freeFunc);
DeleteArrayFunc<int> deleteArrayFunc;
shared_ptr<int> sp2(new int[4], deleteArrayFunc);
return 0;
}
总结:智能指针的存在就是为了更方便、更安全的使用指针
|