cpp笔记
请见:
序言 现代 C++ 教程: 高速上手 C++ 11/14/17/20 - Modern C++ Tutorial: C++ 11/14/17/20 On the Fly (changkun.de)
1. 指针常量和常量指针
1.指针常量:不能修改指针所指向的地址。定义同时必须初始化。
int a = 10;
int* const p = &a; //指针常量
*p = 1; //正确
p = &b; //错误 指针指向的地址不能改变
2.常量指针:不能修改指针所指向地址的内容。但可以改变指针指向的地址。
const int* p = &a; //常量指针
int const* p; //同上
*p = 1; //错误,指针指向的值不能改变
p = &b; //正确
3.指向常量的常指针常量: 二者均不可修改。
const int a = 10;
const int* const p = &a;
4.类中使用const: 常成员函数,只能调用常成员函数和常成员数据。
class A{
public:
A(int x) : num(x), b(x){}
int getNum() const; //const修饰不改变成员变量的函数
const A operator*(const A& left, const A& right); //const修饰成员函数返回值
const getConstNum() const; //二者结合
}
//常成员函数
//1.不能修改类的任何非静态数据成员
//2.不能调用类的任何非const函数
//3.const可以用于对重载函数的区分
//ps:类中const成员变量必须通过初始化列表进行初始化
5.小结:
· 如果const位于*右侧,则修饰指针本身,为指针常量。
· 如果const位于*左侧,则修饰指针所指向的变量,为常量指针。
· 可以将常量指针指向非const数据,但不能用常量指针修改该数据的内容。
· 对于变量来说值传递和引用传递效率差距不大,但对象引用传递效率更高。
2. const
const永远先修饰左边的符号,若左边没有则修饰右边的。
1.const的引用:对常量的引用不能被用作修改它所绑定的对象
const int ci = 1024;
const int &r1 = ci;
r1 = 42; //错误 r1是对常量的引用
int &r2 = ci; //错误 试图让一个非常量的引用指向一个常量对象
const &r3 = 12; //正确(表达式作为初始值)
int i = 42;
int &r1 = i;
const int &r2 = i; //正确 const引用 引用一个非const对象,r1可修改i的值,r2不能
注意:引用并不是一个对象,”常量引用“是对const的引用
允许用任意表达式作为初始值,只要结果能转换成引用的类型即可(见上r3)
double dval = 3.14;
const int &r1 = dval; //正确 编译器会产生如下代码
const int temp = dval; //生成一个临时的整型常量
const int &r1 = templ //r1绑定这个临时量
2.指针和const:常对象地址必须要用常指针存,而常指针未必要指向常对象。
见1.指针常量和常量指针
3.顶层const和底层const: 顶层const表示指针本身是个常量(指针常量)
底层const表示指针指向的对象是一个常量(常量指针)
4.constexpr和常量表达式:值不会改变并且在编译过程就能得到计算结果的表达式
const int max_files = 20; //max_files是常量表达式
int staff_size = 27; //不是 非const
const int sz = get_size(); //不是 具体值知道运行时才得到
# 当确定了变量是一个常量表达式,就将其生成为constexpr类型
constexpr int limit = max_files+1;
4.1 一个constexpr指针的初始值必须是nullptr或者0,或者存于某个固定地址中的对象。
4.2 constexpr中定义了一个指针,则只对指针有效,和其所指的对象无关。
const int *p = nullptr; //指向整形常量的指针
constexpr int *q = nullptr; //指向整数的常量指针
3. 参数传递
1.传值参数:实参的值被拷贝给形参,二者为相互独立的对象。
1.1指针形参
void reset(int *ip){
*ip = 0; //改变ip所指对象的值
ip = 0; //只改变了ip的局部拷贝,实参未被改变
}
int i = 42;
reset(&i); // i = 0
2.传引用参数:形参是引用类型,或者函数被传引用调用
void reset(int &i){
i = 0; //i只是j的一个别名
}
int j = 42;
reset(j); //j = 0
2.1使用引用避免拷贝:拷贝大的类类型对象或容器对象
//形参无需修改时,最后声明为常量引用
bool isShorter(const string &s1, const string &s2){
return s1.size() < s2.size();
}
3.const形参和实参:实参初始化形参时会忽略顶层const
void fun(const int i) { /*能读取i 但不能向i写值*/}
void fun(int i) { /*重复定义了fun(int)*/}
注意:尽量使用常量引用,不能把const对象、字面值、需要类型转换的对象传递给普通的引用形参。
4.数组形参:
4.1 显式传递一个表示数组大小的形参
//const int a[] 等价于 const int* a
void print(const int a[], size_t size){
for(size_t i = 0;i != size; ++i){
cout << a[i] << endl;
}
}
int j[] = {0, 1};
print(j, end(j) - begin(j));
4.2 数组引用形参
//arr是具有10个整数的整型数组的引用
void print(int (&arr)[10]){
for(auto elem : arr)
cout << elem << endl;
}
5.多维数组传递:
1.给定二维数组长度
void print(int a[][4])
2.给定二维长度的指针
void print(int (*p)[4])
3.二维数组指针转为一维数组指针
void print(int *p)
4.二级指针
void print(int **p){
cout << *((int*)p + i * 4 + j) << endl;
}
6.含有可变性惨的函数:所有形参类型相同,可传递initializer_list,类似vector,但其内部值均为常量。
void error_msg(initializer_list<string> il){
for(auto beg = il.begin(); beg != il.end(); ++beg)
cout << *beg << " ";
cout << endl;
}
error_msg({"functionX", expected, actual}); //调用时参数要用{}括起
7.返回数组指针:
typedef int arrT[10];
arrT* fun(int i); //返回一个指向含有十个整数的数组的指针
若不使用别名
int (*func(int i))[10];
//1.func(int i) 需要一个int类型参数
//2.(*func(int i)) 可以对函数调用的结果解引用
//3.(*func(int i))[10] 解引用的结果是一个大小是10的数组
//4.int (*func(int i))[10] 数组中的元素是int类型
c++11中可用尾置返回类型
auto func(int i) -> int(*)[10];
4.函数指针
1.函数指针指向的是函数而非对象。
bool (*pf)(const string&, const string&); //pf是一个指向函数的指针
bool compareLength(const string& s1, const string& s2) {
return s1.size() > s2.size() ? 1 : 0;
}
//则可以使用pf指向compareLength函数
pf = compareLength;
bool b1 = pf("abc", "ac"); //通过pf调用函数compareLength
2.指向不同函数类型的指针不存在转换规则,因此使用指向重载函数的指针必须明确精确匹配函数类型。
void ff(int*);
void ff(unsigned int);
void (*pf1)(unsigned int) = ff; //pf1 指向 ff(unsigned)
void (*pf2)(int) = ff; //错误:没有任何一个ff与形参列表匹配
double (*pf3)(int*) = ff; //错误:ff和pf3返回值不相同
3.函数指针形参
void useBigger(const string& s1, const string& s2, bool (*pf)(const string&, const string&));
useBigger("abc", "bc", comparelength); //自动将函数compare转换成指向该函数的指针
可以使用decltype简化生命
typedef decltype(lengthCompare) Fun; //Fun是函数类型
useBigger("abc", "ab", Fun);
5.容器
5.1:容器操作可能使迭代器失效
当添加元素时,如果容器是vector或string,且存储空间被重新分配,则只向容器的迭代器、指针和引用都会失效。
删除元素时,尾后迭代器总是会失效
auto begin = v.begin(), end = v.end(); //不要保存end返回的迭代器!
//程序会死循环,添加元素时导致end中的迭代器失效
while(begin != end){
++begin;
begin = v.insert(begin, 42);
++begin;
}
5.2访问成员函数返回的是引用
if (!c.empty()){
c.front() = 42;
auto &v = c.back();
v = 1024; //会改变c中的值
auto v2 = c.back(); //auto不会隐式的指明值是否为引用,需要加&
c = 0; //不会改变c的值
}
5.3容器元素是拷贝:将一个对象插入到容器中,实际上插入的是对象值的一个拷贝,而非对象本身。改变容器中的元素与实际对象之间没有任何关联,反之亦然。
6.泛型算法
6.1 泛型算法不会执行容器的操作,只会运行于迭代器之上。同样迭代器令算法不依赖于容器,但算法依赖于元素类型的操作。
int val = 42; //要查找的值
auto result = find(vec.cbegin(), vec.cend(), val); //找到返回指向它,不然为vec.cend()
6.2只读算法:只会读取输入范围内的元素,而从不改变元素,如find、accumulate等 在头文件numeric
int sum = accumulate(vec.begin(), vec.end(), 0); //和的初始值为0
操作两个序列的算法假定第二个序列至少与第一个一样长。
equal(roster1.cbegin(), roster1.cend(), rester1.cbegin());
6.3写容器元素的算法:要确保序列的原大小至少不小于要求算法写入的元素数目。
fill(vec.begin(), vec.end(), 0); //每个元素重置为0
vector<int> vec;
fill_n(vec.begin(), vec.size(), 0); //正确,fill_n接受操作个数
fill_n(vec.begin(), 10, 0); //错误,算法不会检查写操作的范围,向空容器中修改10个数
int a1[] = {1, 2, 3};
int a2[sizeof(a1)/sizeof(*a1)];
auto ret = copy(begin(a1), end(a1), a2); //把a1的内容拷贝给a2
重排容器元素的算法
void elimDups(vector<string> &words){
sort(words.begin(), word.end());
//unique并不会改变元素个数,返回一个指向不重复值范围末尾的迭代器
auto end_unique = unique(words.begin(), words.end());
//删除剩余元素
words.erase(end.unique, words.end());
}
6.4 lambda: 约等于一个未命名的内联函数
基本形式:[capture list](parameter list) -> return type { function body }
- lambda不能有默认参数,调用的是参数永远与形参数目相等。
- 一个lambda只有在捕获列表中捕获一个他所在函数中的局部变量,才能在函数体中使用。
vector<string> words;
words.push_back("aaa");
words.push_back("bbb");
size_t sz = 4;
//获取一个迭代器,指向第一个满足size() >= sz的元素
auto wc = find_if(words.begin(), words.end(), [sz](const string& a) {return a.size() >= sz; });
//输出大于size >= sz的元素数目
auto count = words.end() - wc;
cout << count << endl;
值捕获:被捕获的变量的值实在lambda创建时拷贝,而不是调用时拷贝
void fun1(){
size_t v1 = 42;
auto f = [v1] { return v1; };
v1 = 0;
auto j = f(); //j的值为42,f保存了创建它时v1的拷贝
}
引用捕获:必须保证lambda执行时变量是存在的
void fun2(){
size_t v1 = 42;
auto f2 = [&v1] { return v1; };
v1 = 0;
auto j = f2(); // j为0,f2保存v1的引用,而非拷贝
}
隐式捕获:让编译器根据lambda中的代码推断要使用哪些变量。&表示引用捕获,=表示值捕获
auto wc = find_if(words.begin(), words.end(), [=](const string& a) {return a.size() >= sz; }); //隐式值捕获
可变lambda:若希望改变一个被捕获的变量的值,需加上关键字mutable
void fun3(){
size_t v1 = 42;
auto f = [v1] () mutable { return ++v1; };
v1 = 0;
auto j = f(); //v1 = 43;
}
指定lambda返回类型:若lambda体包含return之外的任何语句,则编译器假定lambda返回void。
/* transform接受三个迭代器和一个可调用对象,前两个为输入序列,第三个为目的位置,当输入迭代器与目标迭代器相同时,transform将输入序列每个元素替换
*/
// 捕获为空,接受一个int参数, 返回一个int值
// 定义返回类型时 必须使用尾置指针返回类型
transform(vi.begin(), vi.end(), vi.begin(), [](int i) -> int
{ if(i < 0) return -i; else return i; });
6.5流迭代器:绑定到输入输出流上,可用来遍历关联的IO流。
- istream_iterator
//从输入流中读入int数据,遇到非int时结束
istream_iterator<int> in_iter(cin);
istream_iterator<int> eof;
vector<int> vec1(in_iter, eof);
for (auto iter : vec1) {
cout << iter << ' ';
}
2. ostream_iterator
//将vec中每个元素写道cout,每个后边加一个空格
vector<int> vec;
vec.push_back(1);
vec.push_back(2);
ostream_iterator<int> out_iter(cout, " ");
for (auto e : vec)
*out_iter++ = e;
cout << endl;
7. 智能指针
智能指针能自动释放所指向的对象。
7.1 shared_ptr:允许多个指针指向同一个对象
1. 基本用法:智能指针类似vector也是模板,创建时需指出指向的类型,默认初始化的智能指针存一个空指针。
shared_ptr<string> p1; //shared_ptr可以指向string
shared_ptr<list<int>> p2; //指向list<int>
//如果p1不空 且 p1指向的string不空
if(p1 && p1->empty())
*p1 = "hi";
最安全的分配和使用动态内存方法是使用make_shared函数
shared_ptr<int> p3 = make_shared<int>(42); //指向一个值为42的int的shared_ptr
shared_ptr<int> p4 = make_shared<int>(); //未指定初始值,进行值初始化(0)
auto p5 = make_shared<vector<string>>(); //使用auto保存
shared_ptr维护一个引用计数器,当其递减为0时释放内存。(拷贝、初始化、做参数、做返回值会递增)
使用动态内存的三种原因:
1.不知道需要多少对象 2.不知道所需对象的准确类型 3.需要在多个对象间共享数据
2.使用new动态分配和创建对象:new无法为分配对象命名,返回一个指向该对象的指针。
int *pi = new int;
string *ps = new string(10, '9');
string *ps = new string;; //默认初始化空string,值未定义
string *ps = new string(); //值初始化空的string,有良好定义的值
auto p1 = new auto(obj); //p指向一个与obj类型相同的对象,仅当括号中有单一初始化器才可以用auto
int *p1 = new (nothrow) int; //如果分配失败,返回一个空指针,定位new
3.空悬指针:指针无效后依然保存着被释放的动态内存地址。
int *p(new int(42));
auto q = p;
delete p; //q造成空悬指针
p = nullptr; //p置nullptr表示不指向任何变量
4.shared_ptr与new结合:用new返回的指针初始化智能指针,不能隐式转换,必须直接初始化
shared_ptr<int> p1(new int(1024));
不要混合使用智能指针与普通指针,不要用get初始化另一个智能指针或为其赋值
p = new int(1024); //错误
p.reset(new int(1024)); //正确
5.shared_ptr内存泄漏问题
此时可以使用weak_ptr,释放流程如下图,最后一步B没有任何智能指针指向,也会被释放。
7.2 unique_ptr:同一时刻只能有一个指向一个对象
1.基本用法:
unique_ptr<string> p1(new string("hhh"));
unique_ptr<string> p2(p1); //错误 不支持拷贝
unique_ptr<string> p3 = p2; //错误 不支持赋值
unique_ptr<string> p2(p1.release()); //将所有权从p1转移给p2,release将p1置空
unique_ptr<string> p3(new string("hhh"));
p2.reset(p3.release()); //放弃p3的控制,将p2指向对象释放,将p3对象给p2,并将p3置空
可以拷贝或赋值一个将要被销毁的unique_ptr(返回一个unique_ptr)
8. move
(42条消息) c++ 之 std::move 原理实现与用法总结_ppipp1109的博客-CSDN博客
/**
* @brief Convert a value to an rvalue.
* @param __t A thing of arbitrary type.
* @return The parameter cast to an rvalue-reference to allow moving it.
*/
template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
return static_cast<typename remove_reference<T>::type&&>(t);
}
//经典用例
string str = "Hello";
vector<string> v;
v.push_back(str);
v.push_back(move(str));
//str 为空 v[0] = v[1] = "hello"
9.引用折叠
A& & -> A&
A& && -> A&
A&& & -> A&
A&& && -> A& &
左值引用会传染,只有纯右值&& && = &&, 沾上一个左值引用就变成左值引用了
10.完美转发
所谓的完美转发,是指std::forward会将输入的参数原封不动地传递到下一个函数中,这个“原封不动”指的是,如果输入的参数是左值,那么传递给下一个函数的参数的也是左值;如果输入的参数是右值,那么传递给下一个函数的参数的也是右值。
template<typename _Tp>
constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }
template<typename _Tp>
constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}
标签:const,string,指向,int,auto,笔记,cpp,指针
From: https://www.cnblogs.com/zhangfirst1/p/17778658.html