目录
5. 新增加容器---静态数组array、forward_list以及unordered系列
1. C++11简介
C++11名字的由来
在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名称。不过由于TC1主要是对C++98标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为C++98/03标准。从C++0x到C++11,C++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率。
2. 列表初始化
引入:C++98中{}的初始化问题
int array1[] = {1,2,3,4,5};
int array2[5] = {0};
vector<int>nums{1,2,3,4};
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <array>
#include <string>
#include <assert.h>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
int x1 = 1;
int x2 = { 2 };//不建议使用
int x3{ 2 }; // 不建议使用
//cout << typeid(x2).name() << endl;//int
//cout << typeid(x2).name() << endl;//int
//本质都是在调用构造函数
Date d1(2023, 8, 22);
Date d2 = { 2023,8,23 };
Date d3{ 2023,8,24 };
//调用支持 list (initializer_list<value_type> il, const allocator_type& alloc = allocator_type());
vector<int> v1 = { 1,2,3,4 };
vector<int> v2{ 1,2,3,4,5 };
list<int> l1{ 5,6,7,8 };
list<int> l2 = { 3,4,5,6 };
//auto x= { 1,2,3,4 };
//cout << typeid(x).name() << endl;
vector<Date>v3 = { d1,d2,d3 };
vector<Date>v4{ d1,d2,d3 };
string s1 = "hello";
//构造
map<string, string>dict = { {"apple","苹果"},{"banana","香蕉"},{"fruit","水果"} };
//赋值重载
initializer_list<pair<const string, string>> kvil = { { "left", "左边" }, { "left", "左边" } };
dict = kvil;
return 0;
}
![](https://img-blog.csdnimg.cn/eed9c48244134fe693a02af6246075a8.png)
3. 变量类型推导
关键字auto
#include <map>
#include <string>
int main()
{
short a = 32670;
short b = 32670;
// c如果给成short,会造成数据丢失,如果能够让编译器根据a+b的结果推导c的实际类型,就不会存
//在问题
short c = a + b;
std::map<std::string, std::string> m1{ {"apple", "苹果"}, {"banana","香蕉"} };
// 使用迭代器遍历容器, 迭代器类型太繁琐
std::map<std::string, std::string>::iterator it = m1.begin();
auto e = m1.begin();//和上面这行等价
while (it != m1.end())
{
cout << it->first << " " << it->second << endl;
++it;
}
return 0;
}
template<class T1, class T2>
T1 Add(const T1& left, const T2& right)
{
return left + right;
}
关键字typeid和decltype
1. 推演表达式类型作为变量的定义类型
int main()
{
int a = 10;
int b = 20;
// 用decltype推演a+b的实际类型,作为定义c的类型
decltype(a+b) c;
cout<<typeid(c).name()<<endl;
return 0;
}
2. 推演函数返回值的类型
void* GetMemory(size_t size)
{
return malloc(size);
}
int main()
{
// 如果没有带参数,推导函数的类型
cout << typeid(decltype(GetMemory)).name() << endl;
// 如果带参数列表,推导的是函数返回值的类型,注意:此处只是推演,不会执行函数
cout << typeid(decltype(GetMemory(0))).name() <<endl;
return 0;
}
比较typeid和decltype
int main()
{
int x = 10;
//typeid(x)y1 = 5;//error
// typeid拿到只是类型的字符串,不能用这个再去定义对象什么的
decltype(x) y1 = 20;
cout << typeid(y1).name() <<" y1:" << y1 << endl;//int
auto y2 = 3.14;
cout << typeid(y2).name()<<" y2:"<<y2 << endl;
return 0;
}
运行结果:
4. 范围for循环
上面我们提到了用迭代器遍历其实挺麻烦的,我们有更直接的方法--范围for
这个代码想来有的小伙伴页见过,作用是统计数组中数字的个数
#include<unordered_map>
#include<vector>
using std::vector;
using std::unordered_map;
int main()
{
unordered_map<int,int> count;
vector<int>nums{ 1,1,2,2,2,3,4,4,4,4,5 };
for (auto num : nums)
{
count[num]++;
}
for (auto e : count)
cout << e.first << "出现了 "<<e.second<<" 次"<<endl;
return 0;
}
实际上范围for的底层是封装了迭代器的。
5. 新增加容器---静态数组array、forward_list以及unordered系列
array官方文档 : cplusplus.com/reference/array/
新增的array主要是为了替代C语言中的静态数组
//C++11新增的array容器测试
int main()
{
const size_t N = 100;
int a1[N];
// C语言数组越界检查,越界读基本检查不出来,越界写是抽查
a1[N];
//a1[N] = 1;
a1[N+5] = 1;
// 越界读写都可以被检查出来
// 实际情况:array用得很少,一方面大家用c数组用惯了
// 用array不如用vector + resize去替代c数组
array<int, N> a2;
a2[N];
a2[N] = 1;
a2[N + 5] = 1;
return 0;
}
6. 默认成员函数控
案例1
//模拟实现的string, 方便观察现象
namespace hy
{
class string
{
public:
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
// 拷贝构造
string(const string& s)
:_str(nullptr)
{
cout << "string(const string& s) -- 拷贝构造(深拷贝)" << endl;
//string tmp(s._str);
//swap(s);
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
// 移动构造
string(string&& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
cout << "string(string&& s) -- 资源转移" << endl;
swap(s);
}
// 拷贝赋值
string& operator=(const string& s)
{
cout << "string& operator=(string s) -- 拷贝赋值(深拷贝)" << endl;
string tmp(s);
swap(tmp);
return *this;
}
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string s) -- 移动赋值(资源移动)" << endl;
swap(s);
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void push_back(char ch)
{
if (_size >= _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
const char* c_str() const
{
return _str;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
hy::string to_string(int value)
{
bool flag = true;
if (value < 0)
{
flag = false;
value = 0 - value;
}
hy::string str;
while (value > 0)
{
int x = value % 10;
value /= 10;
str += ('0' + x);
}
if (flag == false)
{
str += '-';
}
std::reverse(str.begin(), str.end());
return str;
}
// 拷贝构造和移动构造
int main()
{
hy::string ret = to_string(-3456);
hy::string s1("1111111");
hy::string s2(s1);
return 0;
}
![](https://img-blog.csdnimg.cn/52c71d5210a340d98a7b6f2332a75dad.png)
案例2
int main()
{
hy::string str1("hello");
hy::string str2(str1); // 拷贝构造
hy::string str3(std::move(str1)); // 移动构造
std::string s1("hello world");
std::string s2(s1); // 拷贝构造
// std::string s3(s1+s2);
std::string s3 = s1 + s2; // 移动构造
std::string s4 = move(s1);
return 0;
}
案例3
int main()
{
std::vector<hy::string> v;
hy::string s1("hello");
v.push_back(s1);
cout << "----------------------------------" << endl;
v.push_back(hy::string("world"));
//v.push_back("world");
cout << "===================================" << endl;
std::list<hy::string> lt;
//bit::string s1("hello");
lt.push_back(s1);
cout << "----------------------------------" << endl;
lt.push_back(hy::string("world"));
//lt.push_back("world");
return 0;
}
案例4
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{}
// 会默认生成拷贝构造+移动构造
// 不想让Person对象拷贝
//Person(const Person& p) = delete;
/*~Person()
{}*/
private:
hy::string _name;
int _age;
};
int main()
{
Person s1("张三", 7);
Person s2 = s1; // 拷贝构造
Person s3 = std::move(s1); // 移动构造 (没有移动构造,再调用拷贝构造)
//Person s4;
//s4 = std::move(s2);
return 0;
}
7.左值引用VS右值引用
int main()
{
//左值:可以取地址的值-->内存中有位这个变量开辟空间
/*int a = 20;
const int b = 5;
int* p = &a;
*p = 100;
//a,b,p都是左值*/
//以下的p、b、c、*p都是左值
int* p = new int(0);
int b = 1;
const int c = 2;
// 以下几个是对上面左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
//右值:不能取地址
double x = 1.1, y = 2.2;
10;
x + y;
fmin(x, y);
//cout << &10 << endl;
//cout << &(x + y) << endl;
// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
return 0;
}
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
// 万能引用:t既能引用左值,也能引用右值
// 引用折叠
template<typename T>
void PerfectForward(T&& t)
{
// 完美转发:保持t引用对象属性
Fun(std::forward<T>(t));
}
#include"list.h"
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(std::move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(std::move(b)); // const 右值
hy::list<hy::string> lt;
hy::string s1("hello");
lt.push_back(s1);
cout << "----------------------------------" << endl;
//lt.push_back(bit::string("world"));
lt.push_back("world");
return 0;
}
![](https://img-blog.csdnimg.cn/16fe15d2eff74cdab608b61a103a744d.png)
问题:
左值引用可以引用右值吗? 右值引用可以引用左值吗?
示例2:
//有条件的支持
// 左值引用可以引用右值吗? const的左值引用可以
//double& r1 = x + y; //error
const double& r1 = x + y;//有条件的
// 右值引用可以引用左值吗?可以引用move以后的左值
//int&& rr5 = b;
int&& rr5 = move(b);
结论1 :const修饰的左值可以引用右值,因为本身不可修改,因而产生了万能引用
//万能引用
// x既能接收左值,也能接收右值
template<class T>
void Func(const T& x)
{}
结论2 : 右值不能引用左值,但是可以引用move后的左值,使用move有一定风险,要小心使用。
int main()
{
double x = 1.1, y = 2.2;
int&& rr1 = 10;
const double&& rr2 = x + y;
rr1 = 20;
//rr2 = 5.5;//error
cout << rr1 << " " << rr2 << endl;
return 0;
}
应用
//实现一个只能在对上创建对象的类
class HeapOnly
{
public:
HeapOnly()
{
_str = new char[10];
}
~HeapOnly() = delete;
void Destory()
{
delete[] _str;
operator delete(this);
}
private:
char* _str;
//...
};
//使用
int main()
{
//HeapOnly hp1;
//static HeapOnly hp2;
HeapOnly* ptr = new HeapOnly;
//delete ptr;
ptr->Destroy();
//operator delete(ptr);
return 0;
}