pair是一个模板数据类型,其中包含两个数据值,两个数据值可以不同
如 pair<int,string>a(2,"fgh");则a是一个pair类型,它包括两个数据,第一个数据是int型2,第二个数据是string型"fgh"。
由于pair类型的使用比较繁琐,因为如果要定义多个形同的pair类型的时候,可以时候typedef简化声明:
typedef pair<string, string> author;
author pro("May", "Lily");
author joye("James", "Joyce");
对pair对象的操作
-
对于pair类,由于它只有两个元素,分别名为first和second,因此直接使用普通的点操作符即可访问其成员
pair<string, string> a("Lily", "Poly");
string name;
name = pair.second;
在使用map的插入功能时,可以这样来写:
map<string,int> m;
m.insert(pair<string,int>("Jake",3));
C++11允许声明constexpr类型来由编译器检验变量的值是否是一个常量表达式。声明为constexpr的必须是一个常量,并且只能用常量或者常量表达式来初始化。
constexpr int i=3;
constexpr int j=i+1;
constexpr int k=f(); //只有f()是一个constexpr函数时k才是一个常量表达式
一般来说,若果一旦认定变量是一个常量表达式,那就把它声明为constexpr类型
尽管指针和引用都可以定义为constexpr,但是他们的初始值却受到了严格的限制。一个constexpr指针的初始值必须是nullptr或者0,或者是存储某个固定地址的对象。函数体中定义的变量并非存放在固定地址中,因此constexpr指针不可以指向这样的变量。相反的,对于所有函数体之外的对象地址是固定不变的,可以用constexpr来定义;
必须明确一点,在constexpr声明中,如果定义了一个指针,限定符号constexpr仅仅对指针有效,与指针所指对象无关。
const int *p=nullptr; //p是一个指向整型常量的指针(pointer to const)
constexpr int *p1=nullptr; //p1是一个常量指针(const pointer)
处理类型(typedef,uisng,auto,decltype)
一:类型别名是一个名字,它是某种类型的定价。有两种方法定义类型别名:
1.使用typedef关键字,如:
typedef int *Int_Ptr
Int_Ptr p=nullptr; //Int_Ptr是一个int指针类型,这里定义了一个int型指针P
2.使用别名声明(使用using).如:
using Int_Ptr=int*;
Int_Ptr p=nullptr; //与上面完全一样
二:auto类型说明符
编程时,常常需要把表达式的值赋给变量,于是就要求在声明变量时必须知道表达式的类型。然而有的时候并不容易知道表达式的类型。c++11中引入了auto类型说明符,用它就可以让编译器与分析变量的具体类型:
int i=3,j=4;
auto item=i+j; //这时候编译器检验i+j得到的是一个整型,于是auto推断出了item是整型。
使用auto也可以在一条语句中声明多个变量,因为一条语句中最多只有一个数据类型,所以该语句中所有变量初始化必须是一样的:
auto a=2.23,b=0.25; //正确,a,b都是double类型
atuto c=5,d=2.9 ; //错误,c,d类型不同
const的限定
const对象一旦创建后,其值就不可以改变,所以const对象必须初始化。与非const的类型比较,主要区别在于const类型对象执行但是不改变其操作内容。
在默认状态下,const只在文件内有效。在默认状况下,const定义的对象仅限定在文件类起作用,当多个文件中出现了同名的const时其实等同于在不同文件中分别定义了独立的变量。于是我们只在一个文件中定义const对象,而在其他文件中仅仅声明就可以。对于const对象不管是声明还是定义都加extern关键字,这样只需要定义一次就可以了。如下:
//在file1.h文件里面定义i
extern const int i=3; //将i定义为常量3
//在file2.h中引用i时只要声明就可以,避免重定义
extern const int i;
int a=i; //这样就可以使用i了
const的引用被称为对常量的引用(reference to const),引用类型必须与其所引用的对象保持一致,但是在这里有两个例外,第一个例外是在初始化常量引用时允许用任意表达式作为初始值。允许为一个常量引用绑定非常量对象,字面值,甚至是一个表达式:
int i=42;
const int &r1=i; //正确,此时r1与i绑定
const int &r2=42; // 正确,此时绑定一个字面值42
const int &r3=r1*2; //正确,r3是一个常量引用,绑定字面值84
int &r4=42; //错误,非常量引用不可绑定字面值
指向常量的指针不可以用于改变它所指的对象的值。要想存放指向常量的地址,必须使用指向常量的指针。
const int i=9;
int *p1=&i //错误,不可以用一个普通指针指向一个常量
const int*p2=&i; //正确,用常量指针指向常量
*p2=5; //错误,常量指针不可以改变所指向位置的值
常量指针(const pointer)必须初始化,一旦初始化完成,它指向的位置(存放的地址)就不在改变。
int i=4;
int j=5;
int *const p=&i; //p将一直指向i,p是一个常量指针
const int*p1=&j; //p1是一个指向常量的指针,注意const的位置
void*类型的指针
void*是一种特殊的指针类型,可以用来存放任意对象的地址。一个void*指针存放着一个地址,这一点和其他指针类似。不同的是,我们对它到底储存的是什么对象的地址并不了解;
比如:double a=2.3;
int b=5;
void *p=&a;
cout<<p<<endl; //输出了a的地址
p=&b;
cout<<p<<endl; //输出了b的地址
//cout<<*p<<endl;这一行不可以执行,void*指针只可以储存变量地址,不冷直接操作它指向的对象
利用void*可以直接做的事比较有限:拿他和别的指针比较,作为函数的输入或者输出,或者赋值给另外一个void*的指针。不可以操作void*指向的对象。如此一来,内存空间就仅仅是内存空间,没办法访问内存空间指向的对象
nullptr
nullptr出现的目的自然是替换NULL的低位。C++可能会将NULL、0视为同一种东西。这取决于编译器是如何定义的,有的编译器定义NULL为 ( (void * )0) ,有的直接定义为0,这样的化在程序中可能会出现意想不到的错误,例如它会破坏函数的重载功能,考虑下面的重载函数
void function(char *p);
void function(int x);
那么当调用 function(0)
时将会调用哪一个函数呢?这可能会在不同编译器产生不同结果。C++11引入nullptr 专门来区分0和NULL,nullptr的类型是nullptr_t
#include<iostream>
using namespace std;
void test(char*p){
puts("调用的是char *");
}
void test(int x){
puts("调用的是int");
}
int main(){
test(0);
test(nullptr);
return 0;
}
/*
调用的是int
调用的是char *
*/
今后尽量使用nullptr避免使用NULL
constexpr
C++本身具备常量表达式的概念,常量表达式(const expression)是指值不会改变并且在编译过程中就得到计算结果的表达式,如果编译器能够在编译时就把这些表达式直接优化并植入到程序运行时,将能增加程序的性能。C++11 提供了 constexpr 让用户显式的声明函数或对象构造函数在编译器会成为常数,在C++11中常量constexpr修饰的函数可以采用递归形式。
类推类型与区间迭代
在传统 C 和 C++中,参数的类型都必须明确定义,当我们面对一大堆复杂的模板类型时,必须明确的指出变量的类型才能进行后续的编码,这样很不方便,而向python等语言就要显得智能得多。C++11 引入了 auto 和 decltype 这两个关键字实现了类型推导,让编译器来操心变量的类型。这使得 C++ 也具有了和其他现代编程语言一样,某种意义上提供了无需操心变量类型的使用习惯。
auto关键字
使用 auto 关键字是一个存储类型说明符,C++11将用于进行类型推导。这个要求进行显示初始化,
auto m = 120; // m是int类型
auto p = &m; // p是int类型指针
以前遍历一个迭代器需要这样做
vector<int>::iterator it;
for(it = vec.begin(); it != vec.end(); it++){
;
}
有了auto后,遍历方便很多,只需要
for(auto x : vec){
;
}
注意:auto 不能用于函数传参,这将无法通过编译的(考虑重载的问题,我们应该使用模板)。
decltype 关键字
关键字decltype 将变量的类型声明为表达式指定的类型。下面语句的意思是让y的类型与x相同:
int x = 5;
decltype(x) y;
再看下面例子
#include<bits/stdc++.h>
using namespace std;
int main(){
double x;
decltype(&x) y; // 定义y为double类型指针
y = new double{4.0};
cout << *y << endl;
return 0;
}
在定义类模板的时候特别有用,因为只有等到模板实例化后才知道具体类型
#include<bits/stdc++.h>
using namespace std;
template<typename T, typename U>
void func(T a, U b){
decltype (a*b) x;
}
int main(){
func(2, 2.5); // 此时x是double类型
func(2u, 6); // 此时x是unsinged int 类型
return 0;
}
特别注意,decltype指定类型可以是引用或者const,如下例子一样:
#include<bits/stdc++.h>
using namespace std;
int main(){
int a = 1;
int &ra = a;
const int b = 3;
decltype(b) y = 4; // y的类型为const int
int c = 0;
decltype(ra) x = c; // x的类型是int &
return 0;
}
尾返回类型
C++11 增加了一种新语法,在函数名和函数列表后面指定返回类型
double f1(double, int); // 传统语法
auto f2(double, int) -> double; // 新语法 ,返回double 类型
这种设计将给C++带来很多方便,例如定义一个模板类返回2个数的和,传统方法需要写为:
template<class T, class U, class R>
R add(T a, U b){
return a + b;
}
由于不知道返回类型,这样传递返回类型是一种很丑陋的写法,有了后置返回类型,上述代码可以写为:
template<class T, class U>
auto add(T a, U b) -> decltype(a + b){
return a + b;
}