C11
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));
常量表达式(const expression):是指值不会改变并且在编译过程中就得到计算结果的表达式。(运行中得到结果的不能成为常量表达式)。
const int i=3; //是一个常量表达式
const int j=i+1; //是一个常量表达式
int k=23; //k的值可以改变,从而不是一个常量表达式
const int m=f(); //不是常量表达式,m的值只有在运行时才会获取。
constexpr变量:
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)
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*指向的对象。如此一来,内存空间就仅仅是内存空间,没办法访问内存空间指向的对象。
继承关系·
C++继承方式有公有继承,私有继承,保护继承。值得注意的是,基类私有成员在任用任何继承方式下都是隔离的,也就是视派生类为外人。在公有继承中,基类的每个成员在子类中保证相同的访问方式,在基类为public 成员,则在子类也是public成员,在基类为保护成员则在子类也是保护成员。对于保护继承,基类的公有成员和保护成员继承到子类都变成了保护成员。
C++具有单一继承和多重继承。多重继承在实现时并不容易,主要是编译问题,模糊性问题,调试问题也很多,一般只有高级程序员才使用多重继承。如下例子:
#include<iostream>
using namespace std;
class Furniture{
protected :
double weight;
public:
void setWeight(double i){ weight = i; }
double getWeight(){ return weight; }
};
class Bed :virtual public Furniture{
public:
void sleep();
};
class Sofa :virtual public Furniture{
public:
void wacthTV(){};
};
class SleeperSofa :public Sofa, public Bed{
public:
void floadOut(){};
};
int main(){
SleeperSofa f;
f.setWeight(1.0);
}
上面程序中,Furniture是最初的基类,Bed类和Sofa类都从Furniture虚拟继承而来,而SleepSofa则继承于Bed和Sofa类,若不加上virtual关键字,则会产生歧义,这是由于在子类SleepSofa中继承的weight和setWeight到低来自哪里?是继承自Bed类还是,Sofa类,这样就产生了模糊不清的问题,如今的SleepSofa继承了Furniture的两个副本。当加上virtual关键字后,表明,Bed类和Sofa类都从Furniture虚拟继承,这样后代就保证了只继承一个副本,就不会产生歧义。
类推类型与区间迭代
在传统 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;
}
C++1x (本教程中指 C++11/14, 甚至 C++17) 为传统 C++ 注入的大量特性使得整个 C++ 变得更加像一门现代化的语言。C++1x 不仅仅增强了 C++ 语言自身的可用性,auto 关键字语义的修改使得我们更加有信心来操控极度复杂的模板类型。同时还对语言运行期进行了大量的强化,Lambda 表达式的出现让 C++ 具有了『匿名函数』的『闭包』特性,而这一特性几乎在现代的编程语言(诸如 Python/Swift/... )中已经司空见惯,右值引用的出现解决了 C++ 长期以来被人诟病的临时对象效率问题等等。 C++1x 为自身的标准库增加了非常多的工具和方法,诸如在语言层面上提供了 std::thread 支持了并发编程,在不同平台上不再依赖于系统底层的 API,实现了语言层面的跨平台支持;std::regex提供了完整的正则表达式支持等等。
被弃用的特性
弃用不等于废弃,只是用于暗示程序员这些特性将从未来的标准中消失,应该尽量避免使用。但是,已弃用的特性依然是标准库的一部分,并且出于兼容性的考虑,这些特性其实会『永久』保留。
- 如果一个类有析构函数,为其生成拷贝构造函数和拷贝赋值运算符的特性被弃用了。
- 不再允许字符串字面值常量赋值给一个 char *。如果需要用字符串字面值常量赋值和初始化一个char*,应该使用 const char * 或者 auto
char \*str = "hello world!"; // C++11中无法通过编译
const char *str = "hello world!"; // C++11使用
- C++98 异常说明、 unexcepted_handler、set_unexpected() 等相关特性被弃用,应该使用 noexcept。auto_ptr 被弃用,应使用 unique_ptr。
- register 关键字被弃用。
- bool 类型的 ++ 操作被弃用。
- C 语言风格的类型转换被弃用,应该使用 static_cast、reinterpret_cast、const_cast 来进行类型转换。
分类: C++新标准(C++11)
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修饰的函数可以采用递归形式。
上一篇: KMP字符串