c++ 11/14 (一)

本文详细介绍了C++11/14的一些关键特性,包括弃用的特性,如auto_ptr的替换、register关键字的弃用,以及新引入的特性,如nullptr、auto的新用途、decltype、using、初始化列表、lambda表达式、右值引用等。此外,还讨论了模板、面向对象编程、枚举类和std::function等相关内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

弃用特性

(会保留,但终究会从标准中消失) 弃用的特性(1):如果一个类有析构函数,为其生成拷贝构造函数和拷贝赋值运算符的特性被弃用了。**

弃用的特性(2):不再允许字符串字面值常量赋值给一个 char *。如果需要用字符串字面值常量赋值和初始化一个 char *,应该使用 const char * 或者 auto。

弃用的特性(3):C++98 异常说明、 unexpected_handler、set_unexpected() 等相关特性被弃用,应该使用 noexcept。**

弃用的特性(4):auto_ptr 被弃用,应使用 unique_ptr。

弃用的特性(5):register 关键字被弃用。

弃用的特性(6):bool 类型的 ++ 操作被弃用。

弃用的特性(7):C 语言风格的类型转换被弃用,应该使用 static_cast、reinterpret_cast、const_cast 来进行类型转换。

其他诸如参数绑定(C++11 提供了 std::bind 和 std::function)、export 等特性也均被弃用。

C兼容性

.h文件
#ifdef __cplusplus
extern "C" {
#endif

int add(int x, int y);

#ifdef __cplusplus
}
#endif

nullptr 引入

传统C++ : NULL = 0( or NULL = (void*) 0 )
C++11 /14:nullptr_t nullptr 空指针(!= 0)
使用NULL的情况改为使用nullptr

constexpr引入

用户显式的声明函数或对象构造函数在编译器会成为常数,编译器去验证函数应该是个常数(不是常数,会报错?
C++11:

constexpr int fibonacci(const int n) {
    return n == 1 || n == 2 ? 1 : fibonacci(n-1)+fibonacci(n-2);
}

C++14:允许使用局部变量,循环和分支等简单语句

constexpr int fibonacci(const int n) {
    if(n == 1) return 1;
    if(n == 2) return 1;
    return fibonacci(n-1)+fibonacci(n-2);
}

auto 改用途

C :register关键字请求让编译器将变量a直接放入寄存器里面,以提高读取速度 C++:register关键字仍支持,但是c++编译器也有自己的优化方式,即某些变量不用register关键字进行修饰,编译器也会将多次连续使用的变量优化放入寄存器中,例如入for循环的循环变量i。

传统C++ :使用auto修饰的变量,是具有自动存储器的局部变量。如果一个变量没有声明为 register 变量,将自动被视为一个 auto 变量。

for(vector<int>::const_iterator itr = vec.cbegin(); itr != vec.cend(); ++itr)

C++11/14 :auto 任意类型

std::vector<int> arr(5, 100);
for(auto itr= arr.begin(); itr != arr.end(); ++itr);
for(auto &i : arr) ;

auto i=5; //i int
auto arr = new auto(10);//arr int*

int array[] = {1,2,3,4,5};
for(auto &x : array) ;

auto不用于传参,不用于推导数组类型:

//int add(auto x,auto y);

/*
int arr[10] = {0};
 auto auto_arr = arr;
 auto auto_arr2[10] = arr;
*/

上述推导数组哪个不行?

decltype引入

auto 关键字对变量进行类型推导 decltype关键字对表达式进行推导得出类型,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
auto x = 1;
auto y = 2;
decltype(x+y) z;   // z 是一个 int 型的

尾返回类型
传统C++:

template<typename R, typename T, typename U>
R add(T x, U y) {
    return x+y
}//必须明确指出返回类型。但事实上我们并不知道 add() 这个函数会做什么样的操作,获得一个什么样的返回类型。

C++11:

template<typename T, typename U>
auto add(T x, U y) -> decltype(x+y) {
    return x+y;
}// auto 关键字将返回类型后置

C++14:

template<typename T, typename U>
auto add(T x, U y) -> decltype(x+y) {
    return x+y;
}

using引入

1. 另类的typedef:
typedef int (*process)(void *);  
using process = int(*)(void *); // 同上, 更加直观

template <typename T>
using NewType = SuckType<int, T, 1>;    // 合法

2.类中/struct中 使用

  • using父类方法,主要是用来实现可以在子类实例中调用到父类的重载版本(不影响原来的父类与子类的成员关系,但是using可以让子类使用原来不可见的父类成员)
class Base{
  void men()
  {
    cout<<"Base men1"<<endl;
  }
  void men(int value)
  {
    cout<<"Base men2"<<endl;
  }
}
class Sunclass:Base{
	using Base::men;

	void men(int value){
		cout<<"sub men";
	}
}
int main() {
    Subclass s;
    s.men() ;//Base men1
    s.men(3);//sub men"
}
  • 继承构造:子类使用父类的构造
class Base {
public:
    int value1;
    int value2;
    Base() {
        value1 = 1;
    }
    Base(int value) : Base() {                          // 委托 Base() 构造函数
        value2 = 2;
    }
};
class Subclass : public Base {
public:
    using Base::Base;  // 继承构造
};
int main() {
    Subclass s(3);
    std::cout << s.value1 << std::endl;//1
    std::cout << s.value2 << std::endl;//3
}

对象初始化及初始化列表

传统C++: 普通数组、POD (plain old data,没有构造、析构和虚函数的类或结构体)类型都可以使用 {} (初始化列表)进行初始化。 对于类对象的初始化,要么需要通过拷贝构造、要么就需要使用 () 进行。
int arr[3] = {1,2,3};   // 列表初始化

int x=1;//=   ——赋值
int y(1);//() ——初始化
int z{0};
int x = {0};

class Foo {
private:
    int value;
public:
    Foo(int) {}
};
Foo foo(1);// 普通构造初始化
Foo foo2 = foo;//拷贝构造

//vector常用初始化
vector < int > v;
//这时候v的size为0,如果直接进行访问 v[i] 会报错。
//使用 v.resize(n),使用n个0来初始化 
//或者v.resize(n, m) 来初始化后者是使用n个m来初始化。
vector < int > v(n);//使用n个0来初始化 
vector < int > v(n,m);//使用n个m来初始化
vector < int > v = {1,2,3,4,5};//C++11
vector < int > v {1,2,3,4,5};//C++11
vector < int > v(v0);//v0也必须是vector
vector < int > v = v0;//v0也必须是vector
 vector < int > v(p, q);//使用另外一个数组的指针来初始化v,这里即可以使用vector的指针,也可以使用普通数组的指针。 见例:
 int a[3] = { 1,2,3 };
vector<int> v = {1,2,3,4};
vector<int> v2(a, a+2);
for (int i = 0; i < v2.size(); i++)
	cout << v2[i] << " ";          //输出为1 2
cout << endl;
vector<int> v3(v.begin()+1, v.end() - 1);
for (int i = 0; i < v3.size(); i++)
	cout << v3[i] << " ";   		//输出为2 3

	// 一维数组初始化:
    标准方式一: int value[100]; // value[i]的值不定,没有初始化
    标准方式二: int value[100] = {1,2}; // value[0]和value[1]的值分别为1和2,而没有定义的value[i>1],则初始化为0
    指针方式: int* value = new int[n]; // 未初始化
               delete []value;  // 一定不能忘了删除数组空间
    
	//二维数组初始化:
   标准方式一: int value[9][9]; // value[i][j]的值不定,没有初始化
   标准方式二: int value[9][9] = {{1,1},{2}}//value[0][0,1]和value[1][0]的值初始化,其他初始化为0
   指针方式一: int** value = new int* [m];
                for(i) value[i] = new int[n];
                for(i) delete []value[i];
                delete []value; // 多次析构,存储麻烦,未初始化
   指针方式二: int * value = new int[3][4]; // 数组的存储是按行存储的
                delete []value; // 一定要进行内存释放,否则会造成内存泄露
   传递:void Func(int **value);

   指针方式三: int (*value)[n] = new int[m][n];
                delete []value; // n必须为常量,调用直观。未初始化
   
  	传递: void func(int (*value)[n]);
    
   //多维数组初始化:
   指针方式: int * value = new int[m][3][4]; // 只有第一维可以是变量,其他几维必须都是常量,否则会报错
              delete []value; // 一定要进行内存释放,否则会造成内存泄露


C++11:把初始化列表的概念绑定到了类型上,并将其称之为 std::initializer_list,允许构造函数或其他函数像参数一样使用初始化列表,这就为类对象的初始化与普通数组和 POD 的初始化方法提供了统一的桥梁。

#include <initializer_list>

class Magic {
public:
    Magic(std::initializer_list<int> list) {}//初始化列表构造函数
};
Magic magic = {1,2,3,4,5};

void func(std::initializer_list<int> list) {
    return;
}
func({1,2,3});//作为普通函数的形参

C++11:统一初始化理解
意味着我们可以使用{}这种通用的语法在任何需要初始化的地方。
其使用场景主要有以下3种:
(1)类非静态成员指定默认值(不支持小括号)
(2)容器赋值 vector vec = {1, 2, 3};(不支持等号和小括号;)
(3)不支持拷贝操作的对象赋值 unique_ptr p{};(不支持等号)

模板

  • 外部模板引入

简化编译

template class std::vector<bool>;            // 强行实例化
extern template class std::vector<double>;  // 不在该编译文件中实例化模板 
  • 默认模板参数

C++11:

template<typename T = int, typename U = int>
auto add(T x, U y) -> decltype(x+y) {
    return x+y;
}
  • 变长参数模板

一直没明白的部分

面向对象

C++11 :
  • 委托构造:构造函数可以在同一个类中一个构造函数调用另一个构造函数,从而达到简化代码的目的
class Base {
public:
    int value1;
    int value2;
    Base() {
        value1 = 1;
    }
    Base(int value) : Base() {  // 委托 Base() 构造函数
        value2 = 2;
    }
};
  • 继承构造:见using用法
  • 显式虚函数重载

override 和 final:引入 override 关键字将显式的告知编译器进行重载,编译器将检查基函数是否存在这样的虚函数,否则将无法通过编译。final 则是为了防止类被继续继承以及终止虚函数继续重载引入的

struct Base {
    virtual void foo(int);
};
struct SubClass: Base {
    virtual void foo(int) override; // 合法
    virtual void foo(float) override; // 非法, 父类没有此虚函数
};

struct Base {
        virtual void foo() final;
};
struct SubClass1 final: Base {
};                  // 合法

struct SubClass2 : SubClass1 {
};                  // 非法, SubClass 已 final

struct SubClass3: Base {
        void foo(); // 非法, foo 已 final
};
  • 默认函数:

传统C++:编译器会默认为对象生成默认构造函数、复制构造、赋值算符以及析构函数。另外,C++ 也为所有类定义了诸如 new delete 这样的运算符。
C++11:允许显式的声明采用或拒绝编译器自带的函数。编译器产生的默认构造函数与用户定义的构造函数同时存在。

class Magic {
public:
    Magic() = default;  // 显式声明使用编译器生成的构造
    Magic& operator=(const Magic&) = delete; // 显式声明拒绝编译器生成构造
    Magic(int magic_number);
}

枚举类

传统C++:枚举类型会被视作整数,让两种完全不同的枚举类型可以进行直接的比较(虽然编译器给出了检查,但并非所有),枚举类型的枚举值名字不能相同。 C++11:枚举类:不能够被隐式的转换为整数,同时也不能够将其与整数数字进行比较,更不可能对不同的枚举类型的枚举值进行比较。但相同枚举值之间如果指定的值相同,那么可以进行比较;获得枚举值的值时,将必须显式的进行类型转换
enum class week {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
enum class new_enum : unsigned int {//指定枚举的底层类型
    value1,
    value2,
    value3 = 100,
    value4 = 100
};

//显示类型转换
cout <<  static_cast<int>(week ::Sun)<< endl;

//通过重载 << 这个算符来进行输出
template<typename T>
std::ostream& operator<<(typename std::enable_if<std::is_enum<T>::value, 
						std::ostream>::type& stream, const T& e)
//enable_if 在第一个参数为false的时候并不会定义type,只有在第一个参数为true的时候才会定义type为第二个参数
{
    return stream << static_cast<typename std::underlying_type<T>::type>(e);
    //underlying_type 若 T 是完整枚举类型,则提供指名 T 底层类型的成员 typedef type 。
}

Lambda 表达式

ps:每种语言 lambda规则都不太一样啊 **C++11:** [捕获列表] (参数列表) mutable(可选) 异常属性 -> 返回类型 { // 函数体} 注:一般函数的函数名被略去,返回值使用了一个 -> 的形式进行

Lambda 表达式并不能够模板化:参数表不能够泛化,必须明确参数表类型。而auto不能用于参数表。

Lambda 表达式的本质是一个函数对象,当 Lambda 表达式的捕获列表为空时,Lambda 表达式还能够作为一个函数指针进行传递。

using foo = void(int);  // 定义函数指针, using 的使用见上一节中的别名语法
void functional(foo f) {
    f(1);
}

int main() {
    auto f = [](int value) {
        std::cout << value << std::endl;
    };
    functional(f);  // 函数指针调用
    f(1);           // lambda 表达式调用
    return 0;
}

捕获列表:(均为左值捕获)

  • 值捕获
    值捕获的前期是变量可以拷贝,不同之处则在于,被捕获的变量在 lambda 表达式被创建时拷贝,而非调用时才拷贝
void learn_lambda_func_1()
 {
    int value_1 = 1;
    auto copy_value_1 = [value_1] {
        return value_1;
    };//copy_value_1 在创建时就保存了一份 value_1 的拷贝
    value_1 = 100;
    auto stored_value_1 = copy_value_1();
    //stored_value_1 == 1, 而 value_1 == 100.
 }
  • 引用捕获
void learn_lambda_func_1()
 {
    int value_1 = 1;
    auto copy_value_1 = [&value_1] {
        return value_1;
    };//copy_value_1 保存的 value_1 的引用
    value_1 = 100;
    auto stored_value_1 = copy_value_1();
    //stored_value_1 == 100, 而 value_1 == 100.
 }
  • 隐式捕获
    手动书写捕获列表有时候是非常复杂的,这种机械性的工作可以交给编译器来处理,这时候可以在捕获列表中写一个 & 或 = 向编译器声明采用 引用捕获或者值捕获.
    [&] 引用捕获, 让编译器自行推导捕获列表
    [=] 值捕获, 让编译器执行推导应用列表

C++14:

  • 表达式捕获(可右值捕获)
    允许捕获的成员用任意的表达式进行初始化,这就允许了右值的捕获,被声明的捕获变量类型会根据表达式进行判断,判断方式与使用 auto 本质上是相同的。
#include <iostream>
#include <utility>
void learn_lambda_func_3(){
    auto important = std::make_unique<int>(1);//独占指针,不可捕获
    auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int {
        return x+y+v1+(*v2);
    };//std::move 转移为右值
    std::cout << "add(3, 4) = " << add(3, 4) << std::endl;
}
  • 泛型lambda
    Lambda 函数的形式参数可以使用 auto 关键字来产生意义上的泛型:
 auto generic = [](auto x, auto y) {
        return x+y;
 std::cout << "generic(1,2) = " << generic(1, 2) << std::endl;
 std::cout << "generic(1.1,2.2) = " << generic(1.1, 2.2) << std::endl;

函数对象包装器

  • std::function

一种通用、多态的函数封装,它的实例可以对任何可以调用的目标实体进行存储、复制和调用操作

#include <functional>

int foo(int para) {
    return para;
}
int main() {
    // std::function 包装了一个返回值为 int, 参数为 int 的函数
    std::function<int(int)> func = foo;

    int important = 10;
    std::function<int(int)> func2 = [&](int value) -> int {
        return 1+value+important;
    };
   func(10);
   func2(10);
}
  • std::bind/std::placeholder

std::bind 则是用来绑定函数调用的参数的,它解决的需求是我们有时候可能并不一定能够一次性获得调用某个函数的全部参数,通过这个函数,我们可以将部分调用参数提前绑定到函数身上成为一个新的对象。

#include <functional>
#include <iostream>

//函数绑定
int TestFunc(int a, char c, float f)
{
    std::cout << a << std::endl;
    std::cout << c << std::endl;
    std::cout << f << std::endl;
    return a;
}
auto fun1 = std::bind(TestFunc, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
auto fun2 = std::bind(TestFunc, std::placeholders::_2, std::placeholders::_3, std::placeholders::_1);
auto fun3 = std::bind(TestFunc, std::placeholders::_1, std::placeholders::_2, 98.77);
fun1(30, 'C',100.1);
fun2(100.1, 30, 'C');
fun3(30,'C');
fun3(30,'C',8.9);//参数8.9将会忽略掉


using namespace std::placeholders; // 对于 _1, _2, _3...
//成员函数/数据绑定
struct Foo {
    void print_sum(int n1, int n2)
    {
        std::cout << n1+n2 << '\n';
    }
    int data = 10;
};

Foo foo;
auto f3 = std::bind(&Foo::print_sum, &foo, 95, _1);// 绑定指向成员函数指针
f3(5);
auto f4 = std::bind(&Foo::data, _1);// 绑定指向数据成员指针
std::cout << f4(foo) << '\n';
 
// 智能指针亦能用于调用被引用对象的成员
std::cout << f4(std::make_shared<Foo>(foo)) << '\n'
          << f4(std::make_unique<Foo>(foo)) << '\n';

右值引用

右值引用的申明:T &&,其中 T 是类型。右值引用的声明让这个临时值的生命周期得以延长、只要变量还活着,那么将亡值将继续存活。

std::move(#include )这个方法将左值参数无条件的转换为右值
std::forward (#include )执行到右值的有条件转换,若是左值,则传递之后仍然是左值,若是右值,则传递之后仍然是右值。在完美转发实现了参数在传递过程中保持其值属性的功能

  • 意义:

传统 C++ 通过拷贝构造函数和赋值操作符为类对象设计了拷贝/复制的概念,但为了实现对资源的移动操作,调用者必须使用先复制、再析构的方式,否则就需要自己实现移动对象的接口。类似于,搬家的时候是把家里的东西直接搬到新家去,而不是将所有东西复制一份(重买)再放到新家、再把原来的东西全部销毁。没有区分『移动』和『拷贝』的概念,造成了大量的数据移动,浪费时间和空间。右值引用的出现恰好就解决了这两个概念的混淆问题。

#include <iostream>
class A {
public:
    int *pointer;
    A() :pointer(new int(1)) { 
        std::cout << "构造" << pointer << std::endl; 
    }
    // 无意义的对象拷贝
    A(A& a) :pointer(new int(*a.pointer)) { 
        std::cout << "拷贝" << pointer << std::endl; 
    }    
    //自定义类中构造区分移动,拷贝
    A(A&& a) :pointer(a.pointer) { 
        a.pointer = nullptr; 
        std::cout << "移动" << pointer << std::endl; 
    }

    ~A() { 
        std::cout << "析构" << pointer << std::endl; 
        delete pointer; 
    }
};

// 防止编译器优化
A return_rvalue(bool test) {
    A a,b;
    if(test) return a;
    else return b;
}
int main() {
	//自定义类中构造区分移动,拷贝
	//函数返回后,产生一个将亡值,被 A 的移动构造(A(A&&))引用,
	//从而延长生命周期,并将这个右值中的指针拿到保存到了 obj 中,
	//而将亡值的指针被设置为 nullptr,防止了这块内存区域被销毁
    A obj = return_rvalue(false);
    
    
    //容器中实现了移动,如何用
    std::string str = "Hello world.";
    std::vector<std::string> v;
    // 将使用 push_back(const T&), 即产生拷贝行为
    v.push_back(str);
    // 将使用 push_back(const T&&), 不会出现拷贝行为
    // 而整个字符串会被移动到 vector 中,所以有时候 std::move 会用来减少拷贝出现的开销
    // 这步操作后, str 中的值会变为空
    v.push_back(std::move(str));
    return 0;
}
  • 引用坍缩规则&完美转换:

传统 C++ :不能够对一个引用类型继续进行引用
C++11:由于右值引用的出现而放宽了这一做法,从而产生了引用坍缩规则,允许我们对引用进行引用,既能左引用,又能右引用。但是却遵循如下规则:

函数形参类型实参参数类型推导后函数形参类型
T&左引用T&
T&右引用T&
T&&左引用T&
T&&右引用T&&
#include <iostream>
#include <utility>
void reference(int& v) {
    std::cout << "左值引用" << std::endl;
}
void reference(int&& v) {
    std::cout << "右值引用" << std::endl;
}
template <typename T>
void pass(T&& v) {
    std::cout << "普通传参:";
    reference(v);
    std::cout << "std::move 传参:";
    reference(std::move(v));
    std::cout << "std::forward 传参:";
    reference(std::forward<T>(v));

}
int main() {
    std::cout << "传递右值:" << std::endl;
    pass(1);
	//传递右值:
	//普通传参:左值引用(因为 reference函数中的v是引用,所以为左值)
	//std::move 传参:右值引用
	//std::forward 传参:右值引用(std::forward 是保持了原来本为右值引用的转换,所以为完美转换,在传递参数的时候,保持原来的参数类型(左引用保持左引用,右引用保持右引用))

    std::cout << "传递左值:" << std::endl;
    int v = 1;
    pass(v);
	//传递左值:
	//普通传参:左值引用
	//std::move 传参:右值引用
	//std::forward 传参:左值引用

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值