草庐IT

【C++】C++11新特性——类的改进|lambda表达式

命由己造~ 2023-04-05 原文

文章目录

一、类的改进

1.1 默认生成

C++ 的类有四类特殊成员函数, 它们分别是: 默认构造函数、 析构函数、 拷贝构造函数以及拷贝赋值运算符。
C++11 新增了两个:移动构造函数和移动赋值运算符重载
这两个成员函数在上一面介绍过:【C++】C++11新特性——右值引用

这些类的特殊成员函数负责创建、 初始化、 销毁, 或者拷贝类的对象。如果没有显式地为一个类定义某个特殊成员函数, 而又需要用到该特殊成员函数时,则编译器会隐式的为这个类生成一个默认的特殊成员函数。

但是, 如果程序员为类显式的自定义了非默认构造函数, 编译器将不再会为它隐式地生成默认无参构造函数。

class A
{
public:
	A(const A& aa)
		: _a(aa._a)
	{}
private:
	int _a;
};

int main()
{
	A a;// 不存在默认的构造函数
	return 0;
}

而对于移动构造函数和移动赋值运算符重载函数:

1.2 移动构造函数

如果没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器就会自动生成一个默认的移动构造函数
这个默认生成的移动构造函数:
对内置类型按照内置类型的字节序拷贝
对自定义类型则先看这个类型是否实现了移动构造,实现了就用移动构造,否则用拷贝构造

namespace yyh
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		// 拷贝构造
		string(const string& s)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			string tmp(s._str);
			swap(tmp);
		}

		// 移动构造
		string(string&& s)
		{
			swap(s);
			cout << "string(string&& s) -- 移动构造" << endl;
		}

		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}

		// 移动赋值
		string& operator=(string&& s)
		{
			swap(s);
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			return *this;
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
		}

		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;

				_capacity = n;
			}
		}

		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}

			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}

		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;
	};
}

class A
{
public:
private:
	int _a = 1;
	yyh::string _s;
};


1.3 移动赋值重载函数

如果没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器就会自动生成一个默认的移动赋值重载函数。
而编译器生成的移动赋值对内置类型和自定义类型的处理方法跟上面一样。



但是只要我们加上了析构函数 、拷贝构造、拷贝赋值重载中的任意一个。编译器就不会默认生成。

class A
{
public:
	~A() {}
private:
	int _a = 1;
	yyh::string _s;
};

1.4 成员变量缺省值

class A
{
public:
	~A() {}
private:
	int _a = 1;// 缺省值
	yyh::string _s = "aaa";// 缺省值
};

这里的缺省值会在构造/拷贝构造的初始化列表使用:
如果在构造/拷贝构造有这两个的初始化就不会走缺省值,谁不在初始化列表初始化就走谁的缺省值。

1.5 强制生成默认函数的关键字default

上面我们举例子写了析构函数后编译器就不会默认生成移动构造和移动赋值。
但是如果我们必须要写析构,有没有什么办法让编译器默认生成呢?
答案是可以使用default关键字。

class A
{
public:
	A(int a = 1, const char* s = "")
		: _a(a)
		, _s(s)
	{}
	// 只有移动构造没有拷贝构造会匹配歧义
	A& operator=(const A& aa) = default;
	A& operator=(A&& aa) = default;

	A(A&& a) = default;

	~A() {}
private:
	int _a = 1;
	yyh::string _s;
};

int main()
{
	A a;
	A b;
	A c;
	// 无default
	A d(move(b));
	c = move(a);
	return 0;
}

1.6 禁止生成默认函数的关键字delete

1.6.1 C++98防拷贝

我们怎么让一个类对象不能被其他类拷贝呢?
首先我们能想到把拷贝构造写成私有,这样在外部就无法被拷贝。
但是如果有一个内部成员函数会调用拷贝函数怎么办?
在C++98中使用的方法是只声明不定义:

class A
{
public:
	A(){}
	// 只声明不定义
	A(const A& aa);

	~A()
	{
		delete[]_p;
	}

private:
	int* _p = new int[10];
};

int main()
{
	A a;
	A b(a);
	return 0;
}

1.6.1 C++11防拷贝

C++11是使用delete关键字来防止拷贝

class A
{
public:
	A(){}

	A(const A& aa) = delete;

	~A()
	{
		delete[]_p;
	}

private:
	int* _p = new int[10];
};

int main()
{
	A a;
	A b(a);
	return 0;
}

二、lambda表达式

2.1 对比

struct gift
{
	std::string _name;// 名称
	int _vol;// 体积
	double _val;// 价值

	gift(const char* name, int vol, double val)
		: _name(name)
		, _vol(vol)
		, _val(val)
	{}
};

int main()
{
	std::vector<gift> v = { {"苹果", 10, 20.0}, {"梨子", 15, 12.8}, {"香蕉", 11, 10.2} };
	// 比较
	return 0;
}

当我们分别想按照名称、体积、价格来进行排序的时候,我们一般会写三个仿函数。

struct CmpNameLess
{
	bool operator()(const gift& g1, const gift& g2)
	{
		return g1._name < g2._name;
	}
};

struct CmpVolLess
{
	bool operator()(const gift& g1, const gift& g2)
	{
		return g1._vol < g2._vol;
	}
};

struct CmpValLess
{
	bool operator()(const gift& g1, const gift& g2)
	{
		return g1._val < g2._val;
	}
};

我们可以看到这样会写很多的仿函数。我们可能遇到过这种代码:

int main()
{
	std::vector<gift> v = { {"苹果", 10, 20.0}, {"梨子", 15, 12.8}, {"香蕉", 11, 10.2} };
	//sort(v.begin(), v.end(), CmpNameLess());
	sort(v.begin(), v.end(), [](const gift& g1, const gift& g2){
		return g1._name < g2._name; });
	return 0;
}

这里就叫lambda表达式。

2.2 lambda表达式语法

lambda表达式书写格式:

[capture-list](parameters)mutable->return-type{statement}

说明:

[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
(parameters): 参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
mutable: 默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
->returntype: 返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
{statement}: 函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

明白了这些语法我们就可以写一个比较大小的lambda表达式:

[](int x, int y)->bool {return x < y; };
[](int x, int y){return x < y; };

而lambda的本质是一个可调用的对象,我们只能用auto接收。可以像仿函数一样调用。

int main()
{
	// [](int x, int y)->bool {return x < y; };
	auto cmp = [](int x, int y){return x < y; };
	cout << cmp(1, 2) << endl;
	return 0;
}

2.3 捕捉列表

[]捕捉列表:

[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针

这几个都可以在捕捉列表里混合使用,例如:
auto fun = [=, &b]() {};// 所有对象都用传值捕捉,只有b用引用捕捉

我们可以使用lambda来捕获上面定义过的变量。

int main()
{
	int a = 1, b = 2;
	auto add = [a](int x) {return x + a; };
	cout << add(b);
	return 0;
}

mutable:

当我们想要交换两个变量时:

int main()
{
	int a = 1, b = 2;
	auto Swap = [a, b]()
	{
		int tmp = a;
		a = b;
		b = tmp;
	};
	return 0;
}


所以这里我们要加上mutable,让传进来的参数可以被修改。

int a = 1, b = 2;
auto Swap = [a, b]()mutable
{
	int tmp = a;
	a = b;
	b = tmp;
};
Swap();

但是我们调试发现a和b的值都没有被改变,原因是捕获列表默认是传值,并不会改变原来的值。

int main()
{
	int a = 1, b = 2;
	auto Swap = [&a, &b]()
	{
		int tmp = a;
		a = b;
		b = tmp;
	};
	Swap();
	cout << a << " " << b << endl;
	return 0;
}

当然也可以使用[&]引用传递所有内容

auto Swap = [&]()
{
	int tmp = a;
	a = b;
	b = tmp;
};

这里就可以看出来mutable没有什么用处。

2.4 函数对象与lambda表达式

struct Add
{
	int operator()(int x, int y)
	{
		return x + y;
	}
};

int main()
{
	// 函数对象
	Add sum1;
	sum1(1, 2);
	// lambda表达式
	auto sum2 = [](int a, int b) {return a + b; };
	sum2(1, 2);
	return 0;
}


实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()



有关【C++】C++11新特性——类的改进|lambda表达式的更多相关文章

  1. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

  2. ruby-on-rails - 使用 config.threadsafe 时从 lib/加载模块/类的正确方法是什么!选项? - 2

    我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co

  3. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  4. 没有类的 Ruby 方法? - 2

    大家好!我想知道Ruby中未使用语法ClassName.method_name调用的方法是如何工作的。我头脑中的一些是puts、print、gets、chomp。可以在不使用点运算符的情况下调用这些方法。为什么是这样?他们来自哪里?我怎样才能看到这些方法的完整列表? 最佳答案 Kernel中的所有方法都可用于Object类的所有对象或从Object派生的任何类。您可以使用Kernel.instance_methods列出它们。 关于没有类的Ruby方法?,我们在StackOverflow

  5. ruby - Rails 关联 - 同一个类的多个 has_one 关系 - 2

    我的问题的一个例子是体育游戏。一场体育比赛有两支球队,一支主队和一支客队。我的事件记录模型如下:classTeam"Team"has_one:away_team,:class_name=>"Team"end我希望能够通过游戏访问一个团队,例如:Game.find(1).home_team但我收到一个单元化常量错误:Game::team。谁能告诉我我做错了什么?谢谢, 最佳答案 如果Gamehas_one:team那么Rails假设您的teams表有一个game_id列。不过,您想要的是games表有一个team_id列,在这种情况下

  6. ruby - 安装libv8(3.11.8.13)出错,Bundler无法继续 - 2

    运行bundleinstall后出现此错误:Gem::Package::FormatError:nometadatafoundin/Users/jeanosorio/.rvm/gems/ruby-1.9.3-p286/cache/libv8-3.11.8.13-x86_64-darwin-12.gemAnerroroccurredwhileinstallinglibv8(3.11.8.13),andBundlercannotcontinue.Makesurethat`geminstalllibv8-v'3.11.8.13'`succeedsbeforebundling.我试试gemin

  7. ruby - 正则表达式将非英文字母匹配为非单词字符 - 2

    @raw_array[i]=~/[\W]/非常简单的正则表达式。当我用一些非拉丁字母(具体来说是俄语)尝试时,条件是错误的。我能用它做什么? 最佳答案 @raw_array[i]=~/[\p{L}]/使用西里尔字符进行测试。引用:http://www.regular-expressions.info/unicode.html#prop 关于ruby-正则表达式将非英文字母匹配为非单词字符,我们在StackOverflow上找到一个类似的问题: https://

  8. ruby - 正则表达式在哪个位置失败? - 2

    我需要一个非常简单的字符串验证器来显示第一个符号与所需格式不对应的位置。我想使用正则表达式,但在这种情况下,我必须找到与表达式相对应的字符串停止的位置,但我找不到可以做到这一点的方法。(这一定是一种相当简单的方法……也许没有?)例如,如果我有正则表达式:/^Q+E+R+$/带字符串:"QQQQEEE2ER"期望的结果应该是7 最佳答案 一个想法:你可以做的是标记你的模式并用可选的嵌套捕获组编写它:^(Q+(E+(R+($)?)?)?)?然后你只需要计算你获得的捕获组的数量就可以知道正则表达式引擎在模式中停止的位置,你可以确定匹配结束

  9. ruby - 为什么当我调用类的实例方法时,初始化不显示为方法? - 2

    我正在写一篇关于在Ruby中几乎一切都是对象的博客文章,我试图通过以下示例来展示这一点:classCoolBeansattr_accessor:beansdefinitialize@bean=[]enddefcount_beans@beans.countendend所以从类中我们可以看出它有4个方法(当然,除非我错了):它可以在创建新实例时初始化一个默认的空bean数组它可以计算它有多少个bean它可以读取它有多少个bean(通过attr_accessor)它可以向空数组写入(或添加)更多bean(也通过attr_accessor)但是,当我询问类本身它有哪些实例方法时,我没有看到默认

  10. ruby-on-rails - Rails 中同一个类的多个关联的最佳实践? - 2

    我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来

随机推荐