底层const与顶层const

顶层const指的是被修饰变量本身无法改变,底层const指的是通过限制指针或者限制引用更改内容的const

通过以下代码来说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void test1()
{
int n = 1;
int m = 2;
int *const p1 = &n;
const int * p2 = &n;
// 第一个const 仅修饰p1
// p1指向的n可以被修改,但是p1自身不能被修改
(*p1)+=2;
std::cout<<n<<'\n';
// p1=&m;
// p2自身可以被修改,但是(*p2)不能被修改
// (*p2)++;
p2=&m;
// 这不代表p2指向的东西不能被修改
m+=50;
std::cout<<*p2<<'\n';
// 关于赋值:一般来说等号右边非const可以转化成const,不能反之
// 等号左右两边的底层const必须一致,或者右边的非const可以转化为const
int *p3 = p1;
const int * p4 = p1;
//这是不合法的,因为p2是底层const,但是p5未被声明为底层const
//int *p5 = p2;
const int *p6 = p2;
}

void test2()
{
//对于STL的迭代器
std::vector<int> v;
for(int i=1;i<=10;i++) v.push_back(i);
const std::vector<int>::iterator it1 = v.begin();
//和 int * const p1 相似,自身值不能被修改,但是可以修改指向的值
*it1 = 10;
//++it1;
for(auto u:v) std::cout<<u<<" ";
std::cout<<'\n';
//10 2 3 4 5 6 7 8 9 10
std::vector<int>::const_iterator it2 = v.begin();
// *it2=1;
it2++;
std::cout<<*it2<<'\n';
for(auto u:v) std::cout<<u<<" ";
std::cout<<'\n';
}

struct mmm{
int a,b;
bool operator <(const mmm &c)
// 要想编译通过,此处必须添加const
// 因为常成员函数不能更新任何数据成员
const {
return a<c.a;
}
};

void test3()
{
std::priority_queue<mmm> q;
q.push(mmm{1,2});
}

Template

首先 template <typename T> 声明一个为 T 的模板。

Function Template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <typename T>
// 这里直接引用,优化一下速度
const T Add(const T &a,const T &b)
{
return a+b;
}

int main()
{
double a=1.1,b=1.9;
int result1=Add<int>(a,b);
int result2=Add<double>(a,b);
a=3.3,b=5.5;
int result3=Add<double>(a,b);
std::cout<<result1<<" "<<result2<<" "<<result3<<'\n';
// 2 3 8
}

Class Template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<bits/stdc++.h>

template <typename T>
class MMM{
private:
T num1,num2;
public:
MMM(T a,T b):num1(a),num2(b){}
T add();
};

template <typename T>
T MMM<T>::add()
{
return num1+num2;
}


int main()
{
MMM<decltype(5.2)> a(5.2,5.5);
std::cout<<a.add()<<'\n';
}

原始字符串

由于有些时候\n并不是表示换行,以及我们的目的不是输出其他的特殊字符,因此可能会用到原始字符串来表示这个字符串

而使用方法为 R"xxx(str)xxx" xxx为任意门描述性词语,str为我们的字符串,开头和结尾的 xxx 必须一致。

另外 \ 也可以用 \\ 表示,但是总有特殊情况,还是得用原始字符串

reference

1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
std::string s = "M:\nexo\ncmmm";
std::cout<<s<<'\n';
s = R"/description(M:\nexo\ncmmm)/description";
std::cout<<s<<'\n';
/*
M:
exo
cmmm
M:\nexo\ncmmm
*/
}

nullptr

C++ 中 NULL 可能会被 define0,在 CPP 中,最好把 NULL 改成 nullptr (会自动类型转换),但是有的编译器会有 definenullptr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void f(int *p)
{
std::cout<<"*p\n";
}

void f(int p)
{
std::cout<<"p\n";
}

int main()
{
int *p1=NULL;
int *p2=nullptr;
int *p4=p2;
// ambiguous
// f(NULL);
// G++ 定义成了 __null,其功能大致相当于 nullptr
// 别的地方NULL基本定义成了0(当然我没用别的编译器试过你可以当我在胡扯)
}

CPP ref上面的Possible implementation

1
2
3
#define NULL 0
//since C++11
#define NULL nullptr

常量表达式 constexpr

定义常量

虽然 G++ 允许变量作为数组长度,但是你不能只用G++

constexpr 方便区分是不是真常量,例如 f(const int x) 这里的 x 实际上不是真的常量,可以视作只读的变量

对于 CPP 内置的数据可以直接用constexpr修饰,但是对于自定义的 struct 以及 class,需要这样写:

1
2
3
4
5
6
7
8
9
struct MMM{
int id,rk;
};

int main()
{
constexpr MMM t{1,2};
std::cout<<t.id<<" "<<t.rk<<'\n';
}

修饰函数

函数必须有返回值,并且函数必须返回一个常量。声明和定义都必须在要调用的函数前面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
constexpr int func()
{
// 只能出现下面的语句以及常量表达式。在class,struct也一样。
// using,typedef,static_assert,return
constexpr int a =100;
return a;
}

template<typename T>
constexpr T mmm(T t)
{
return t;
// 如果t为变量,自动忽略constexpr
}

struct m3{
const int id;
int rk;
constexpr m3(const int _id,int _rk) : id(_id),rk(_rk)
{
}
// 构造函数必须为空,采用初始化列表为成员赋值
};

int main()
{
constexpr MMM t{1,2};
constexpr m3 mm(14,2);
}

auto

auto a= xx ,xx 必须为常量