范围for

for (declaration : expression)
{
	statement
}
  • expression部分是一个对象,用于表示一个序列。
    • 比如说花括号括起来的初始值列表,数组,或vector、string对象等。
    • 共同的特点:能返回迭代器的begin和end成员。
  • declaration部分负责定义一个变量,该变量将被用于访问序列中的基础元素。
    • 每次迭代,declaration部分的变量会被初始化为expression部分的下一个元素值。
    • 确保类型相容最简单的办法是使用auto类型说明符,如果需要写操作必须声明成引用类型。
#include <iostream>
using namespace std;

int main()
{
	int arr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	for (auto temp : arr)
	{
		cout << temp << ends;
	}

	return 0;
}

lambda函数

基本形式:[capture list] (params list) mutable exception attribute-> return type { function body }

  • capture list : 捕获外部变量列表
    • [] 空捕获列表
    • [变量名,...] 默认以值的形式捕获指定的多个外部变量(用逗号分隔),如果引用捕获,需要显示声明(使用&说明符)
    • [this] 以值的形式捕获this指针
    • [=] 以值的形式捕获所有外部变量(隐式捕获列表)
    • [&] 以引用形式捕获所有外部变量(隐式捕获列表)
    • [=,&x] 变量x以引用形式捕获,其余变量以传值形式捕获
    • [&, x] 变量x以值的形式捕获,其余变量以引用形式捕获
  • params list : 形参列表
    • 参数列表中不能有默认参数
    • 不支持可变参数
    • 所有参数必须有参数名
  • mutable : 用来说用是否可以修改捕获的变量
  • exception : 说明lambda表达式是否抛出异常以及何种异常
  • attribute : 用来声明属性
  • return type : 返回类型
  • function body : 函数体

函数对象:

  • 一元函数:接受一个参数如f(x)。如果返回一个布尔值,则称为谓词。
  • 二元函数:接受两个参数如f(x,y)。如果返回一个布尔值,则称为二元谓词。

例子:

#include <iostream>
#include <vector>
#include <algorithm>

int main()
{
	std::vector<int> some_list = {25, 44, 52, 136, 33};
	int total = 0;
	std::for_each(begin(some_list), end(some_list), [&](int x)
	{
		total += x;
	});
	std::cout << total;

	return 0;
}

chrono计时

#include <chrono>   
using namespace std;
using namespace chrono;

// system_clock 替换成 steady_clock也可以
auto start = system_clock::now();
// do something...
auto end   = system_clock::now();
auto duration = duration_cast<microseconds>(end - start);
cout <<  "花费了" 
     << double(duration.count()) * microseconds::period::num / microseconds::period::den 
     << "秒" << endl;

可以为数据成员提供一个类内初始值

例子:

class Screen
{
private:
    int cursor = 0;
    int height = 0;
};

右值

最原始的左值和右值定义可以追溯到C语言时代,左值是可以出现在赋值符的左边和右边,然而右值只能出现在赋值符的右边。
在C++里,这种方法作为初步判断左值或右值还是可以的,但不只是那么准确了。
你要说C++中的右值到底是什么,这真的很难给出一个确切的定义。
你可以对某个值进行取地址运算,如果不能得到地址,那么可以认为这是个右值。

右值可以分为两种:一个是将亡值( xvalue, expiring value),另一个则是纯右值( prvalue, PureRvalue):

  • 纯右值:非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和 lambda 表达式等
  • 将亡值:与右值引用相关的表达式,比如,T&& 类型函数的返回值、 std::move 的返回值等。

右值引用就是对一个右值进行引用的类型。
因为右值是匿名的,所以我们只能通过引用的方式找到它。
无论声明左值引用还是右值引用都必须立即进行初始化,因为引用类型本身并不拥有所绑定对象的内存,只是该对象的一个别名。
通过右值引用的声明,该右值又“重获新生”,其生命周期与右值引用类型变量的生命周期一样,只要该变量还活着,该右值临时量将会一直存活下去。

move语义

为什么要move语义呢?
它可以让你写出更高效的代码。很多情况下会发生对象之间的深拷贝,如果堆内存很大,这个拷贝的代价也就非常大,在某些情况下,如果想要避免对象的深拷贝,就可以使用右值引用进行性能的优化。

#include <iostream>
using namespace std;

class Test
{
public:
    Test() : m_num(new int(100))
    {
        cout << "construct: my name is jerry" << endl;
    }

    Test(const Test& a) : m_num(new int(*a.m_num))
    {
        cout << "copy construct: my name is tom" << endl;
    }

    ~Test()
    {
        delete m_num;
    }

    int* m_num;
};

Test getObj()
{
    Test t;
    return t;
}

int main()
{
    Test t = getObj();
    cout << "t.m_num: " << *t.m_num << endl;
    return 0;
};

如果能够使用临时对象已经申请的资源,既能节省资源,还能节省资源申请和释放的时间,如果要执行这样的操作就需要使用右值引用了,右值引用具有移动语义,移动语义可以将资源(堆、系统对象等)通过浅拷贝从一个对象转移到另一个对象这样就能减少不必要的临时对象的创建、拷贝以及销毁,可以大幅提高 C++ 应用程序的性能。

#include <iostream>
using namespace std;

class Test
{
public:
    Test() : m_num(new int(100))
    {
        cout << "construct: my name is jerry" << endl;
    }

    Test(const Test& a) : m_num(new int(*a.m_num))
    {
        cout << "copy construct: my name is tom" << endl;
    }

    // 添加移动构造函数
    Test(Test&& a) : m_num(a.m_num)
    {
        a.m_num = nullptr;
        cout << "move construct: my name is sunny" << endl;
    }

    ~Test()
    {
        delete m_num;
        cout << "destruct Test class ..." << endl;
    }

    int* m_num;
};

Test getObj()
{
    Test t;
    return t;
}

int main()
{
    Test t = getObj();
    cout << "t.m_num: " << *t.m_num << endl;
    return 0;
};

移动构造中使用了右值引用,会将临时对象中的堆内存地址的所有权转移给对象t,这块内存被成功续命,因此在t对象中还可以继续使用这块内存。
对于需要动态申请大量资源的类,应该设计移动构造函数,以提高程序效率。
需要注意的是,我们一般在提供移动构造函数的同时,也会提供常量左值引用的拷贝构造函数,以保证移动不成还可以使用拷贝构造函数。

转移和完美转发

如果想要使用左值初始化一个右值引用需要借助 std::move () 函数,使用std::move方法可以将左值转换为右值。使用这个函数并不能移动任何东西,而是和移动构造函数一样都具有移动语义,将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝。

template<class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) _NOEXCEPT 	// forward _Arg as movable
{
    return (static_cast<remove_reference_t<_Ty>&&>(_Arg));
}

第一条原则是引用折叠原则:

  • A& & 折叠成 A&
  • A& && 折叠成 A&
  • A&& & 折叠成 A&
  • A&& && 折叠成 A&&

第二条是特殊模板参数推导原则:

  1. 如果函数模板传进的是个A类型的左值,那么T被决议为A&。
  2. 如果函数模板传进的是个A类型的右值,那么T被决议为A。

假设有一个函数foo,我们写出如下函数,把接受到的参数转发给foo:

template<class T>
void fwd(TYPE t)
{
	foo(t);
}

如果TYPE是T的话,假设foo的参数引用类型,我会修改传进来的参数,那么fwd(t)和foo(t)将导致不一样的效果。
如果TYPE是T&的话,那么fwd传一个右值进来,没法接受,编译出错。
如果TYPE是T&,而且重载个const T&来接受右值,看似可以,但如果多个参数呢,你得来个排列组合的重载,因此是不通用的做法。

std::forward应用于forwarding reference,代码看起来如下:

template<class T>
void fwd(T&& t)
{
	foo(std::forward<T>(t));
}

forward可以如下实现:

template<class T>
T&& forward(typename remove_reference<T>::type& t)
{
	return static_cast<T&&>(t);
}

来看看完美转发是怎么工作的,我们预期当传进fwd的参数是左值,从forward返回的是左值引用;传进的是右值,forward返回的是右值引用。假设传给fwd是A类型的左值,那么T被推导为A&:

void fwd(A& && t)
{
	foo(std::forward<A&>(t));
}

forward<A&>实例化:

A& && forward(typename remove_reference<A&>::type& t)
{
	return static_cast<A& &&>(t);
}

引用折叠后:

A& forward(A& t)
{
	return static_cast<A&>(t);
}

符合预期。

再看看传入fwd是右值时,那么T被推导为A:

void fwd(A && t)
{
	foo(std::forward<A>(t));
}

forward<A>实例化如下:

A&& forward(typename remove_reference<A>::type& t)
{
	return static_cast<A&&>(t);
}

也就是:

A&& forward(A& t)
{
	return static_cast<A&&>(t);
}

符合预期。

例子:

#include <iostream>
using namespace std;

template<typename T>
void printValue(T& t)
{
    cout << "l-value: " << t << endl;
}

template<typename T>
void printValue(T&& t)
{
    cout << "r-value: " << t << endl;
}

template<typename T>
void testForward(T && v)
{
    printValue(v);
    printValue(move(v));
    printValue(forward<T>(v));
    cout << endl;
}

int main()
{
    testForward(520);
    int num = 1314;
    testForward(num);
    testForward(forward<int>(num));
    testForward(forward<int&>(num));
    testForward(forward<int&&>(num));

    return 0;
}

测试结果:

l-value: 520
r-value: 520
r-value: 520

l-value: 1314
r-value: 1314
l-value: 1314

l-value: 1314
r-value: 1314
r-value: 1314

l-value: 1314
r-value: 1314
l-value: 1314

l-value: 1314
r-value: 1314
r-value: 1314

分析:

  • testForward(520); 函数的形参为未定引用类型 T&&,实参为右值,初始化后被推导为一个右值引用
    • printValue(v); 已命名的右值 v,编译器会视为左值处理,实参为左值
    • printValue(move(v)); 已命名的右值编译器会视为左值处理,通过 move 又将其转换为右值,实参为右值
    • printValue(forward<T>(v));forward 的模板参数为右值引用,最终得到一个右值,实参为 右值
  • testForward(num); 函数的形参为未定引用类型 T&&,实参为左值,初始化后被推导为一个左值引用
    • printValue(v); 实参为左值
    • printValue(move(v)); 通过 move 将左值转换为右值,实参为右值
    • printValue(forward<T>(v));forward 的模板参数为左值引用,最终得到一个左值引用,实参为左值
  • testForward(forward<int>(num));forward 的模板类型为 int,最终会得到一个右值,函数的形参为未定引用类型 T&& 被右值初始化后得到一个右值引用
    • printValue(v); 已命名的右值 v,编译器会视为左值处理,实参为左值
    • printValue(move(v)); 已命名的右值编译器会视为左值处理,通过 move 又将其转换为右值,实参为右值
    • printValue(forward<T>(v));forward 的模板参数为右值引用,最终得到一个右值,实参为右值
  • testForward(forward<int&>(num));forward 的模板类型为 int&,最终会得到一个左值,函数的形参为未定引用类型 T&& 被左值初始化后得到一个左值引用
    • printValue(v); 实参为左值
    • printValue(move(v)); 通过 move 将左值转换为右值,实参为右值
    • printValue(forward<T>(v));forward 的模板参数为左值引用,最终得到一个左值,实参为左值
  • testForward(forward<int&&>(num));forward 的模板类型为 int&&,最终会得到一个右值,函数的形参为未定引用类型 T&& 被右值初始化后得到一个右值引用
    • printValue(v); 已命名的右值 v,编译器会视为左值处理,实参为左值
    • printValue(move(v)); 已命名的右值编译器会视为左值处理,通过 move 又将其转换为右值,实参为右值
    • printValue(forward<T>(v));forward 的模板参数为右值引用,最终得到一个右值,实参为右值

默认模板参数

当所有模板参数都有默认参数时,函数模板的调用如同一个普通函数。但对于类模板而言,哪怕所有参数都有默认参数,在使用时也必须在模板名后跟随 <> 来实例化。

函数模板的默认模板参数在使用规则上和其他的默认参数也有一些不同,它没有必须写在参数表最后的限制。这样当默认模板参数和模板参数自动推导结合起来时,书写就显得非常灵活了。我们可以指定函数模板中的一部分模板参数使用默认参数,另一部分使用自动推导。

当默认模板参数和模板参数自动推导同时使用时(优先级从高到低):

  • 如果可以推导出参数类型则使用推导出的类型
  • 如果函数模板无法推导出参数类型,那么编译器会使用默认模板参数
  • 如果无法推导出模板参数类型并且没有设置默认模板参数,编译器就会报错。

模板模板参数

模板模板参数是将一个模板作为参数传递给另一个模板。‌ 这种方式允许在模板中使用不同类型的容器或类,从而增加了代码的灵活性和复用性。

语法如下:

template<typename T, template<typename U> class Container>
class MyClass { ... };

例子:

#include <iostream>
#include <string>
#include <vector>
using namespace std;

template<typename T>
using vec = vector<T, allocator<T>>;

template<typename T, 
	template<class>
	class Container
>
class MyClass
{
public:
	Container<T> c;

};


int main()
{
	MyClass<int, vec> c;
	
	return 0;
}

可变模板参数

定义:

template <class... T>
void f(T... args);

获取参数个数:

template <class... T>
void f(T... args)
{
	cout << sizeof...(args) << endl; //打印变参的个数
}

f(); // 0
f(1, 2); // 2
f(1, 2.5, ""); // 3

递归函数方式展开

#include <iostream>
using namespace std;

// 递归终止函数
template <class T>
void print(T t)
{
	cout << "parameter " << t << endl;
}

// 展开函数
template <class T, class ...Args>
void print(T head, Args... rest)
{
	cout << "parameter " << head << endl;
	print(rest...);
}

int main(void)
{
	print(1, 2, 3, 4);
	return 0;
}

逗号表达式展开

#include <iostream>
using namespace std;

template <class T>
void printarg(T t)
{
	cout << t << endl;
}

template <class ...Args>
void expand(Args... args)
{
	int arr[] = { (printarg(args), 0)... };
}

int main(void)
{
	expand(1, 2, 3, 4);
	return 0;
}

模版偏特化和递归方式展开

#include <iostream>
using namespace std;

// 前向声明
template<typename... Args>
struct Sum;

// 基本定义
template<typename First, typename... Rest>
struct Sum<First, Rest...>
{
	enum { value = Sum<First>::value + Sum<Rest...>::value };
};

// 递归终止
template<typename Last>
struct Sum<Last>
{
	enum { value = sizeof(Last) };
};

int main(void)
{
	cout << Sum<int, double, short>::value << endl;
	return 0;
}

继承方式展开

#include <iostream>
using namespace std;

// 整型序列的定义
template<int...>
struct IndexSeq {};

// 继承方式,开始展开参数包
template<int N, int... Indexes>
struct MakeIndexes: MakeIndexes<N - 1, N - 1, Indexes...> {};

// 模板特化,终止展开参数包的条件
template<int... Indexes>
struct MakeIndexes<0, Indexes...>
{
	typedef IndexSeq<Indexes...> type;
};

int main()
{
	using T = MakeIndexes<3>::type;
	cout << typeid(T).name() << endl;	// struct IndexSeq<0,1,2>
	return 0;
}

可调用对象

可调用对象有如下几种定义:

  • 函数指针
  • 具有operator()成员函数的类对象(仿函数)
  • 可被转换为函数指针的类对象
    using func_ptr = void(*)(int, string);
    struct Test
    {
    	static void print(int a, string b)
    	{
    		cout << "name: " << b << ", age: " << a << endl;
    	}
    
    	// 将类对象转换为函数指针
    	operator func_ptr()
    	{
    		return print;
    	}
    };
    
  • 类成员函数指针或者类成员指针

可调用对象包装器

std::function是可调用对象的包装器。它是一个类模板,可以容纳除了类成员(函数)指针之外的所有可调用对象。通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟执行它们。

头文件:#include <functional>
std::function<返回值类型(参数类型列表)> diy_name = 可调用对象;

例子:

#include <iostream>
#include <string>
#include <functional>
using namespace std;

int add(int a, int b)
{
	cout << a << " + " << b << " = " << a + b << endl;
	return a + b;
}

class T1
{
public:
	static int sub(int a, int b)
	{
		cout << a << " - " << b << " = " << a - b << endl;
		return a - b;
	}
};

class T2
{
public:
	int operator()(int a, int b)
	{
		cout << a << " * " << b << " = " << a * b << endl;
		return a * b;
	}
};

using func_ptr = void(*)(int, string);
struct T3
{
	static void print(int a, string b)
	{
		cout << "name: " << b << ", age: " << a << endl;
	}

	// 将类对象转换为函数指针
	operator func_ptr()
	{
		return print;
	}
};

int main(void)
{
	// 绑定一个普通函数
	function<int(int, int)> f1 = add;
	// 绑定以静态类成员函数
	function<int(int, int)> f2 = T1::sub;
	// 绑定一个仿函数
	T2 t;
	function<int(int, int)> f3 = t;
	// 绑定一个可被转换为函数指针的类对象
	T3 tt;
	function<void(int, string)> f4 = tt;

	// 函数调用
	f1(9, 3);
	f2(9, 3);
	f3(9, 3);
	f4(9, "xxx");

	return 0;
}

因为回调函数本身就是通过函数指针实现的,使用对象包装器可以取代函数指针的作用
例子:

#include <iostream>
#include <functional>
using namespace std;

class A
{
public:
    // 构造函数参数是一个包装器对象
    A(const function<void()>& f) : callback(f)
    {
    }

    void notify()
    {
        callback(); // 调用通过构造函数得到的函数指针
    }
private:
    function<void()> callback;
};

class B
{
public:
    void operator()()
    {
        cout << "我是要成为海贼王的男人!!!" << endl;
    }
};

int main(void)
{
    B b;
    A a(b); // 仿函数通过包装器对象进行包装
    a.notify();

    return 0;
}

可调用对象绑定器

头文件:#include <functional>
std::bind(待绑定的函数对象/函数指针/成员函数指针,参数绑定值1,参数绑定值2,...,参数绑定值n);

  • bind的第一个参数是待绑定的函数对象或者函数指针,之后跟随多个参数以设定待绑定函数的参数绑定方式。待绑定函数有多少个参数,则bind后便需要多少个参数以一一声明其参数的绑定方法.当参数绑定为某一固定值时,则其对应参数绑定值可以使一个变量或常量.当需要将参数与绑定所生成的函数对象的某个参数相关联时,则需要用到在标准中预定义的几个常量_1、_2、_3等.这些常量声明在std::placeholders命名空间内.
  • 在将一个R (T::*ptr)(Arg0,Arg1,...)形式的成员函数指针ptr用bind绑定参数时,bind的第一个绑定的参数是成员函数的调用者,随后跟随成员函数的参数绑定方式.例如bind(ptr,a,b,c)将会调用a.*ptr(b,c)。当采用_n常量将首参数与函数对象的参数相关联时,所生成的函数对象自动可接受T类型的引用及指针类型,无需再进行封装.但要想调用外部数据的成员函数,还需要用ref()、cref()来包装或者绑定一个对该变量的指针.

两大作用:

  • 将可调用对象与其参数一起绑定成一个仿函数。
  • 将多元(参数个数为n,n>1)可调用对象转换为一元或者(n-1)元可调用对象,即只绑定部分参数。

例子:

#include <iostream>
#include <memory>
#include <functional>

using namespace std::placeholders;
using namespace std;

class A
{
public:
	void print(int n1, int n2, int n3)
	{
		cout << n1 << " " << n2 << " " << n3 << endl;
	}
};

int main()
{
	A a;
	// 类成员函数需要绑定该类的this指针  
	auto f1 = bind(&A::print, &a, _2, 22, _1);
	f1(44, 55);
	return 0;
}

统一初始化

一致性初始化,使用列表初始化避免缩窄转换错误。
内部是引用了一个array容器的,并且拷贝也只是浅拷贝。
扩大了初始化列表的适用范围,使其可用于所有内置类型和用户定义的类型。
如:

#include <iostream>
using namespace std;

class Test
{
public:
    Test(int) {}
private:
    Test(const Test &);
};

int main(void)
{
    Test t1(520);
    Test t2 = 520; // 在vs中没有错误 g++编译会提示 拷贝构造函数是私有的。
    Test t3 = { 520 };
    Test t4{ 520 };
    int a1 = { 1314 };
    int a2{ 1314 };
    int arr1[] = { 1, 2, 3 };
    int arr2[]{ 1, 2, 3 };

	int * p = new int{520};
	double b = double{52.134};
	int * array = new int[3]{1,2,3};

    return 0;
}

类和结构为聚合类型的条件:

  • 只包含公有和非静态数据成员,而不包含私有或者受保护成员。
  • 不包含任何虚成员函数
  • 只涉及公有继承
  • 不包含用户定义的构造函数

begin和end函数

参数是数组,返回指向数组首元素和尾元素的下一个位置。

#include <iostream>
using namespace std;

int main()
{
       int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

       cout << begin(arr) << ends;
       cout << end(arr) << ends;

       return 0;
}

委托构造函数

使用当前类的其他构造函数来帮助当前构造函数初始化。换而言之,就是可以将当前构造函数的部分(或者全部)职责交给本类的另一个构造函数。
与普通构造函数的区别:

  • 相同点:两者都有一个成员初始值列表与一个函数体
  • 不同点:委托构造函数的成员初始值列表只有一个唯一的参数,就是构造函数(当然不能是当前的构造函数)
    例如:
#include <iostream>
#include <string>

class A 
{
private:
	int _a;
	std::string _s;
public:
	// 普通构造函数
	A(std::string s, int a) : _a(a), _s(s) 
	{
		std::cout << "执行 普通构造函数 函数体" << std::endl;
	}
	// 委托构造函数
	A(int a) : A("测试A(int a)", a) 
	{
		std::cout << "执行 委托构造函数A(int a) 函数体" << std::endl;
	}
	A(std::string s) : A(s, 100) 
	{
		std::cout << "执行 委托构造函数A(std::string s) 函数体" << std::endl;
	}
	A() : A("测试A()", 4) { }
	A(std::ostream &out) : A() 
	{
		out << "我就是打酱油的\n";
	}

	// 打印对象内容
	void display() const 
	{
		std::cout << "---------------------" << std::endl;
		std::cout << "s = " << _s << std::endl;
		std::cout << "a = " << _a << std::endl;
		std::cout << "---------------------" << std::endl;
	}
};

int main()
{
	A a("测试A(std::string s, int a)", 1);
	a.display();

	A b(2);
	b.display();

	A c("测试A(std::string s)");
	c.display();

	A d(std::cout);
	d.display();
	return 0;
}

运行结果:

执行 普通构造函数 函数体
---------------------
s = 测试A(std::string s, int a)
a = 1
---------------------
执行 普通构造函数 函数体
执行 委托构造函数A(int a) 函数体
---------------------
s = 测试A(int a)
a = 2
---------------------
执行 普通构造函数 函数体
执行 委托构造函数A(std::string s) 函数体
---------------------
s = 测试A(std::string s)
a = 100
---------------------
执行 普通构造函数 函数体
我就是打酱油的
---------------------
s = 测试A()
a = 4
---------------------
  • 这种链式的构造函数调用不能形成一个闭环(死循环),否则会在运行期抛异常。
  • 如果要进行多层构造函数的链式调用,建议将构造函数的调用的写在初始列表中而不是函数体内部,否则编译器会提示形参的重复定义。
  • 在初始化列表中调用了代理构造函数初始化某个类成员变量之后,就不能在初始化列表中再次初始化这个变量了。

可以解决冗余代码:

#include <iostream>
using namespace std;

class Test
{
public:
    Test() {};
    Test(int max)
    {
        this->m_max = max > 0 ? max : 100;
    }

    Test(int max, int min)
    {
        this->m_max = max > 0 ? max : 100;              // 冗余代码
        this->m_min = min > 0 && min < max ? min : 1;   
    }

    Test(int max, int min, int mid)
    {
        this->m_max = max > 0 ? max : 100;             // 冗余代码
        this->m_min = min > 0 && min < max ? min : 1;  // 冗余代码
        this->m_middle = mid < max && mid > min ? mid : 50;
    }

    int m_min;
    int m_max;
    int m_middle;
};
#include <iostream>
using namespace std;

class Test
{
public:
    Test() {};
    Test(int max)
    {
        this->m_max = max > 0 ? max : 100;
    }

    Test(int max, int min):Test(max)
    {
        this->m_min = min > 0 && min < max ? min : 1;
    }

    Test(int max, int min, int mid):Test(max, min)
    {
        this->m_middle = mid < max && mid > min ? mid : 50;
    }

    int m_min;
    int m_max;
    int m_middle;
};

继承构造函数

可以让派生类直接使用基类的构造函数,而无需自己再写构造函数,尤其是在基类有很多构造函数的情况下,可以极大地简化派生类构造函数的编写。先来看没有继承构造函数之前的处理方式:

#include <iostream>
#include <string>
using namespace std;

class Base
{
public:
    Base(int i) :m_i(i) {}
    Base(int i, double j) :m_i(i), m_j(j) {}
    Base(int i, double j, string k) :m_i(i), m_j(j), m_k(k) {}

    int m_i;
    double m_j;
    string m_k;
};

class Child : public Base
{
public:
    Child(int i) :Base(i) {}
    Child(int i, double j) :Base(i, j) {}
    Child(int i, double j, string k) :Base(i, j, k) {}
};

通过测试代码可以看出,在子类中初始化从基类继承的类成员,需要在子类中重新定义和基类一致的构造函数,这是非常繁琐的,C++11 中通过添加继承构造函数这个新特性完美的解决了这个问题,使得代码更加精简。

继承构造函数的使用方法是这样的:通过使用 using 类名::构造函数名(其实类名和构造函数名是一样的)来声明使用基类的构造函数,这样子类中就可以不定义相同的构造函数了,直接使用基类的构造函数来构造派生类对象。

#include <iostream>
#include <string>
using namespace std;

class Base
{
public:
    Base(int i) :m_i(i) {}
    Base(int i, double j) :m_i(i), m_j(j) {}
    Base(int i, double j, string k) :m_i(i), m_j(j), m_k(k) {}

    int m_i;
    double m_j;
    string m_k;
};

class Child : public Base
{
public:
    using Base::Base;
};

更多推荐