第7章 改变思考方式

7.1 指针空值

指针空值的引入

NUL的定义:#define NULL 0

指针初始化

int * myptr = 0;
int * myptr = NULL;

将NULL 定义为0 有时候会出现二义性比如:

 void  fun(char *ptr);
 void fun(int i);

调用 fun(NULL); 时候本意是参数为空指针 调用fun(char* ptr) 最后却调用了 fun(int i)

C++ 11 引入了 nullptr cstddef头文件中
typedef decltype(nullptr) nullptr_t
其中 nullptr_t 为指针空值类型
nullptr是指针空值常量 一个编译时期的常量

针对nullptr_t类型C++ 严格规定了数据之间的关系,常见的规则简单列举如下:

  1. 所有定义为nullptr_t类型的数据都是等价的,行为也是完全一致。

  2. nullptr_t类型数据可以隐式转换成任意一个指针类型。

  3. nullptr_t类型数据不能转换为非指针类型,即使使用reinterpret_cast<nullptr_t>()的方式也是不可以的。

  4. nullptr_t类型数据不适用于算术运算表达式。

  5. nullptr_t类型数据可以用于关系运算表达式,但仅能与nullptr_t类型数据或者指针类型数据进行比较,当且仅当关系运算符为==、<=、>=等时返回true。

如果nullptr用在模板中的时候,模板只会把nullptr当做nullptr_t类型来推导如下:

#include <iostream>
using namespace std;
templatetypename Tvoid g(T*t){}
templatetypename Tvoid h(T t){}
int main(){
  g(nullptr);//编译失败,nullptr的类型是nullptr_t,而不是指针
  g((float*)nullptr);//推导出T=float
  h(0);//推导出T=int
  h(nullptr);//推导出T=nullptr_t
  h((float*)nullptr);//推导出T=float*
}

nullptr与void* 的大小是相同的即:

sizeof(nullptr_t) == sizeof(void*)

C++ 中nullptr 是隐式转换 (void*)0需要显示转换

int *ptr = nullptr // 正确
int *ptr = (void *)0 ; // 编译不过

7.2 默认函数控制

编译器会帮程序员自定义类型(类、结构体)生成一些未定义的函数。这样的函数被称为默认函数。如果定义了两个参数的构造函数,则不带参数的构造函数不会默认生成了。

  1. 构造函数 ( className() )
  2. 拷贝构造函数 ( className(const className& instance) )
  3. 拷贝赋值函数( className& operator =(const className& instance) )
  4. 移动构造函数 ( className(const className&& instance) ) 右值引用为参数
  5. 移动拷贝函数(className& operator =(const className&& instance))右值引用为参数
  6. 析构函数

禁用某个函数

className(const className& instance) = delete;

显示使用默认生成函数

className() = default;

注意:非特殊成员函数(构造函数、赋值函数、析构函数)不能使用default

使用空实体{} 跟 使用 = default表现相同,为啥还用default ?

class MyClass {
public:
    MyClass() = default; // 显式地定义默认构造函数
    MyClass(const MyClass&) = default; // 显式地定义拷贝构造函数
    MyClass& operator=(const MyClass&) = default; // 显式地定义拷贝赋值运算符
    ~MyClass() = default; // 显式地定义析构函数
};

7.3 lambda函数

以一个lambda的示例来开启对Lambda表达式的解读

#include <algorithm>
#include <cmath>
void abssort(float* x, unsigned n) {
    std::sort(x, x + n,
        // Lambda expression begins
        [](float a, float b) {
            return (std::abs(a) < std::abs(b));
        } // end of lambda expression
    );
}

sort函数应该传递一个排序规则的函数,但是这个示例直接用lambda表达式代替,简化了代码并提高了可读性。

7.3.1 Lambda表达的语法定义

在这里插入图片描述

  1. 捕获列表。在C++规范中也称为Lambda导入器, 捕获列表总是出现在Lambda函数的开始处。实际上,[]是Lambda引出符。编译器根据该引出符判断接下来的代码是否是Lambda函数,捕获列表能够捕捉上下文中的变量以供Lambda函数使用。
  2. 参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号“()”一起省略。
  3. 可变规格。mutable修饰符, 默认情况下Lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空)。
  4. 异常说明。用于Lamdba表达式内部函数抛出异常。
  5. 返回类型。 追踪返回类型形式声明函数的返回类型。我们可以在不需要返回值的时候也可以连同符号”->”一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导。
  6. lambda函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量。

7.3.2 Lambda表达式参数详解

7.3.2.1 Lambda捕获列表

  • []表示不捕获任何变量
auto function = ([]{
		std::cout << "Hello World!" << std::endl;
	}
);
function();
  • [var]表示值传递方式捕获变量var
int num = 100;
auto function = ([num]{
		std::cout << num << std::endl;
	}
);
function();
  • [=]表示值传递方式捕获所有父作用域的变量(包括this)
int index = 1;
int num = 100;
auto function = ([=]{
			std::cout << "index: "<< index << ", " 
                << "num: "<< num << std::endl;
	}
);
function();
  • [&var]表示引用传递捕捉变量var
int num = 100;
auto function = ([&num]{
		num = 1000;
		std::cout << "num: " << num << std::endl;
	}
);
function();
  • [&]表示引用传递方式捕捉所有父作用域的变量(包括this)
  • [this]表示值传递方式捕捉当前的this指针
  • [=, &a, &b]表示以引用传递的方式捕捉变量a和b,以值传递方式捕捉其它所有变量。
  • [&, a, this]表示以值传递的方式捕捉变量a和this,引用传递方式捕捉其它所有变量。
  • [=,a]这里已经以值传递方式捕捉了所有变量,但是重复捕捉a了,会报错的;
  • [&,&this]这里&已经以引用传递方式捕捉了所有变量,再捕捉this也是一种重复。

7.3.2.2 Lambda参数列表

除了捕获列表之外,Lambda还可以接受输入参数。参数列表是可选的,并且在大多数方面类似于函数的参数列表。

auto function = [] (int first, int second){
    return first + second;
};
function(100, 200);

7.3.2.3 可变规格mutable

mutable修饰符, 默认情况下Lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空)。

#include <iostream>
using namespace std;
int main()
{
   int m = 0;
   int n = 0;
   [&, n] (int a) mutable { m = ++n + a; }(4);
   cout << m << endl << n << endl;
}

7.3.2.4 异常说明

你可以使用 throw() 异常规范来指示 Lambda 表达式不会引发任何异常。与普通函数一样,如果 Lambda 表达式声明 C4297 异常规范且 Lambda 体引发异常,Visual C++ 编译器将生成警告 throw() 。

int main() // C4297 expected 
{ 
 	[]() throw() { throw 5; }(); 
}

7.3.2.5 返回类型

Lambda表达式的返回类型会自动推导。除非你指定了返回类型,否则不必使用关键字。返回型类似于通常的方法或函数的返回型部分。但是,返回类型必须在参数列表之后,并且必须在返回类型->之前包含类型关键字。如果Lambda主体仅包含一个return语句或该表达式未返回值,则可以省略Lambda表达式的return-type部分。如果Lambda主体包含一个return语句,则编译器将从return表达式的类型中推断出return类型。否则,编译器将返回类型推导为void。

auto x1 = [](int i){ return i; };

7.3.2.6 Lambda函数体

Lambda表达式的Lambda主体(标准语法中的复合语句)可以包含普通方法或函数的主体可以包含的任何内容。普通函数和Lambda表达式的主体都可以访问以下类型的变量:

  • 捕获变量
  • 形参变量
  • 局部声明的变量
  • 类数据成员,当在类内声明this并被捕获时
  • 具有静态存储持续时间的任何变量,例如全局变量
#include <iostream>
using namespace std;
int main()
{
   int m = 0;
   int n = 0;
   [&, n] (int a) mutable { m = ++n + a; }(4);
   cout << m << endl << n << endl;
}

7.3.3 Lambda表达式工作原理

编译器会把一个Lambda表达式生成一个匿名类的匿名对象,并在类中重载函数调用运算符,实现了一个operator()方法。

auto print = []{cout << “Hello World!” << endl; };

编译器会把上面这一句翻译为下面的代码:

class print_class{
public:
	void operator()(void) const
	{
		cout << "Hello World!" << endl;
	}
};
// 用构造的类创建对象,print此时就是一个函数对象
auto print = print_class();

仿函数(functor)又称为函数对象,是一个能行使函数功能的类。仿函数的语法几乎跟普通的函数调用一样,只不过作为仿函数的类,必须重载()运算符,仿函数与Lambda表达式是一致的。

#include <iostream>
#include <string>
using namespace std; 
class Functor{
public:
    void operator() (const string& str) const{
        cout << str << endl;
    }
};
int main(){
    Functor myFunctor;
    myFunctor("Hello world!");
    return 0;
}

7.3.4 Lambda表达式适用场景

  • for_each应用示例
int a[4] = {11, 2, 33, 4};
sort(a, a+4, [=](int x, int y) -> bool { return x%10 < y%10; } );
for_each(a, a+4, [=](int x) { cout << x << " ";} );
  • find_if应用示例
int x = 5;
int y = 10;
deque<int> coll = { 1, 3, 19, 5, 13, 7, 11, 2, 17 };
auto pos = find_if(coll.cbegin(), coll.cend(), [=](int i) {                 
    return i > x && i < y;
});
  • remove_if应用示例
std::vector<int> vec_data = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int x = 5;
vec_data.erase(std::remove_if(vec.date.begin(), vec_data.end(), [](int i) { 
    return n < x;}), vec_data.end());
std::for_each(vec.date.begin(), vec_data.end(), [](int i) { 
    std::cout << i << std::endl;});
  • 多线程应用场景
#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>

int main()
{
    // vector 容器存储线程
    std::vector<std::thread> workers;
    for (int i = 0; i < 5; i++) 
    {
        workers.push_back(std::thread([]() 
        {
            std::cout << "thread function\n";
        }));
    }
    std::cout << "main thread\n";

    // 通过 for_each 循环每一个线程
    // 第三个参数赋值一个task任务
    // 符号'[]'会告诉编译器我们正在用一个匿名函数
    // lambda函数将它的参数作为线程的引用t
    // 然后一个一个的join
    std::for_each(workers.begin(), workers.end(), [](std::thread &t;) 
    {
        t.join();
    });

    return 0;
}
  • 函数指针与Function
#include <iostream>
#include <functional>
using namespace std;
int main(void){
    int x = 8, y = 9;
    auto add = [](int a, int b) { return a + b; };
    std::function<int(int, int)> Add = [=](int a, int b) { return a + b; };

    cout << "add: " << add(x, y) << endl;
    cout << "Add: " << Add(x, y) << endl;

    return 0;
}
  • 函数入参
using FuncCallback = std::function<void(void)>;
void DataCallback(FuncCallback callback)
{
	std::cout << "Start FuncCallback!" << std::endl;
	callback();
	std::cout << "End FuncCallback!" << std::endl;
}

auto callback_handler = [&](){
	std::cout << "This is callback_handler";
};

DataCallback(callback_handler);
  • 13
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值