C++11——lambda表达式

本文详细介绍了C++11中的Lambda表达式,包括其用法、语法和原理。Lambda表达式简化了仿函数的创建,使得在代码中可以直接定义匿名函数,减少了类的定义和命名问题。文中通过示例展示了Lambda如何用于排序操作,并对比了传统仿函数和Lambda表达式的区别。此外,还探讨了Lambda表达式的捕捉列表、参数和返回值类型,以及mutable关键字的作用。最后,通过汇编视角揭示了Lambda表达式的底层实现,指出其本质是编译器自动生成的仿函数类。

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

目录

前言

一.lambda表达式用法

二.lambda表达式语法

 三.lambda表达式的原理


前言

        在显示生活中,我们在用手机购物时。总是可以在页面上看到下面这样的选项。

        我们知道底层这是通过排序来完成的,但是当我们实现时,要写多个排序算法,写多个仿函数来实现不同变量的比较。

比如下面代码:

struct CompareNameSmall;
struct CompareNameBig;
struct ComparePriceSmall;
struct ComparePriceBig;

class Goods{
	friend struct CompareNameSmall;
	friend struct CompareNameBig;
	friend struct ComparePriceSmall;
	friend struct ComparePriceBig;

private:
	string _name;
	double _price;
public:
	Goods(string name, double price)
		:_name(name)
		,_price(price)
	{}
			
};

//仿函数
struct CompareNameSmall{
	bool operator()(const Goods& g1, const Goods& g2){
		return g1._name < g2._name;
	}
};
struct CompareNameBig{
	bool operator()(const Goods& g1, const Goods& g2){
		return g1._name > g2._name;
	}
};
struct ComparePriceSmall{
	bool operator()(const Goods& g1, const Goods& g2){
		return g1._price < g2._price;
	}
};
struct ComparePriceBig{
	bool operator()(const Goods& g1, const Goods& g2){
		return g1._price > g2._price;
	}
};


int main(){
	Goods gds[] = { { "苹果", 2.5 }, { "香蕉", 3.0 }, { "梨", 3.5 } };

	sort(gds, gds + (sizeof(gds) / sizeof(gds[0])), CompareNameSmall());
	sort(gds, gds + (sizeof(gds) / sizeof(gds[0])), CompareNameBig());
	sort(gds, gds + (sizeof(gds) / sizeof(gds[0])), ComparePriceSmall());
	sort(gds, gds + (sizeof(gds) / sizeof(gds[0])), ComparePriceBig());

	system("pause");
	return 0;
}

随着C++的发展,人们开始觉得上面的写法太复杂了。每次为了实现一个比较算法,都需要重新定义一个类,如果每次的比较逻辑不一样,还要实现多个类,特别是在相同类的命名上。并且如果不能达到见名知义,我们还得去找对应的仿函数,才能知道它的功能,这给编程者带来了极大的不便。

        因此在C++11语法中出现了lambda表达式。

一.lambda表达式用法

class Goods{

public:
	string _name;
	double _price;

	Goods(string name, double price)
		:_name(name)
		, _price(price)
	{}

};


int main(){
	Goods gds[] = { { "苹果", 2.5 }, { "香蕉", 3.0 }, { "梨", 3.5 } };
    //使用lambda表达式

	sort(gds, gds + (sizeof(gds) / sizeof(gds[0])), [](const Goods& g1, const Goods& g2)->bool{return g1._name < g2._name; });
	sort(gds, gds + (sizeof(gds) / sizeof(gds[0])), [](const Goods& g1, const Goods& g2)->bool{return g1._name > g2._name; });
	sort(gds, gds + (sizeof(gds) / sizeof(gds[0])), [](const Goods& g1, const Goods& g2)->bool{return g1._price < g2._price; });
	sort(gds, gds + (sizeof(gds) / sizeof(gds[0])), [](const Goods& g1, const Goods& g2)->bool{return g1._price > g2._price; });

	system("pause");
	return 0;
}

 可以实现和上面一样的效果。虽然一样要写这么多函数,但是不需要面临去定义很多类和命名问题,并且不需要在去找函数。

但是我们发现lambda表达式的格式还是很奇怪的,下面来介绍一下lambda表达式的写法。

二.lambda表达式语法

lambda表达式书写格式:[捕捉列表](参数)mutable—>返回值类型{ 函数体 } 

  • [捕捉列表]:该列表总是出现在lambda表达式的起始位置,编译器根据[]来判断接下来的代码是否为lambda表达式,捕捉列表能够捕捉当前作用域中的变量,供lambda函数使用。
    • [val]:表示以传值方式捕捉变量val
    • [=]:表示以传值方式捕捉当前作用域中的变量,包括this指针。
    • [&val]:表示以引用方式传递捕捉变量val。
    • [&]:表示以引用方式传递捕捉当前作用域中的所有变量,包括this指针。
    • [this]:表示以传值方式捕捉当前的this指针。
  • (参数):参数列表。与普通函数参数列表使用相同。如果不需要传递参数,可以连同"()"一起省略。
  • mutable:默认情况下,lambda函数总是一个const函数,捕捉的传值参数具有常性,mutable可以取消常性。使用mutable修饰符时,参数列表不能省略,即使参数为空。
  • —>返回值类型:返回值类型。使用追踪返回类型形式声明函数的返回值类型,没有返回值此部分可省略。返回值类型明确的情况下,也可省略,由编译器推导。
  • {函数体}:在函数体内除了可以使用参数外,还能使用捕捉的变量。

注意:在lambda表达式中,参数列表和返回值类型都可省略,而捕捉列表和函数体可以为空。所以最简单的lambda表达式为:[]{},该表达式不能做任何事。

int main(){
	//最简单的lambda表达式
	[]{};

	//捕捉当前作用域的变量,没有参数,编译器推导返回值类型。
	int a = 1;
	int b = 2;
	[=]{return a + b; };

	//使用和仿函数差不多
	auto fun1 = [&](int c){b = a + c; };
	fun1(10);
	cout << a << " " << b << endl;

	auto fun2 = [&](int c)->int{return a + c; };
	fun2(20);
	cout << fun2(20) << endl;

	//传值捕捉
	int x = 1;
	int y = 2;
	auto add0 = [x, y]()mutable->int{ x *= 2;//捕捉传递传值具有常性
									return x + y; };
	cout << add0() << endl;

	auto add1 = [&x, y]()->int{ x *= 2;//捕捉传递引用不具有常性
							return x + y; };
	cout << add1() << endl;

	auto add2 = [](int s, int m)->int{ s *= 2;//参数不具有常性
								return s + m; };
	system("pause");
	return 0;
}

        从上面要注意的一点是:捕捉列表传值传递具有常性,要加mutable,传引用传递不具有常性,参数列表不具有常性。

捕捉列表的要和捕捉参数变量名相同,传值传递是当前作用域变量的拷贝。

int main(){
	//最简单的lambda表达式
	[]{};

	//捕捉当前作用域的变量,没有参数,编译器推导返回值类型。
	int a = 1;
	int b = 2;

	//auto fun1 = [x, y]()->int{return x + y; };//编译错误,要和捕捉参数名相同
	//传值传递是捕捉变量的拷贝,实际外面的a,b没有交换
	auto swap1 = [a, b]()mutable{int z = a; a = b; b = z; };
	swap1();//注意还需要调用
	cout << a << " " << b << endl;

	//传引用才能真正修改
	auto swap2 = [&a, &b]{int z = a; a = b; b = z; };
	swap2();
	cout << a << " " << b << endl;
	return 0;
}

 注意构造完对象后,对象调用,函数才起作用。

注意点:

  • 语法上捕捉列表可由多个捕捉项组成,并以逗号隔开。捕捉项不能重复传递,否则会导致编译错误。比如:当前作用域已经有了变量a,捕捉设为[=,a],=已经捕捉过a了,编译时会报错。
  • 捕捉列表只能捕捉当前作用域的局部变量,作用域以外的局部变量或者非局部变量都会报错。
  • lambda表达式之间不能赋值,即使看起来类型相同。
void (*PF)();
int main(){


	auto f1 = []{cout << "hello world" << endl; };
	auto f2 = []{cout << "hello world" << endl; };
	// 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了
	//f1 = f2; // 编译失败--->提示找不到operator=()

	// 允许使用一个lambda表达式拷贝构造一个新的副本
	auto f3(f2);
	f3();

	// 可以将lambda表达式赋值给相同类型的函数指针
	PF = f2;
	PF();
	return 0;

}

 三.lambda表达式的原理

        首先我们先来比较一下仿函数和lambda表达式

        我们发现仿函数的使用和lambda表达式差不多。

class Rate
{
public:
	Rate(double rate) : _rate(rate)
	{}
	double operator()(double money, int year)
	{
		return money * _rate * year;
	}
private:
	double _rate;
};
int main()
{
	// 函数对象
	double rate = 0.49;
	Rate r1(rate);
	r1(10000, 2);
	// lamber
	auto r2 = [=](double monty, int year)->double{return monty*rate*year; };
	r2(10000, 2);
	return 0;
}

         通过汇编来查看lambda表达式部分:

        lambda表达式原理:实际编译器在全局作用域自动生成了一个类,在类中重载了operator(), operator()函数的内容就是lambda表达式的内容。

        可以理解成lambda表达式底层还是仿函数。本来时要程序员编写,现在变成了编译器自动生成,我们看起来跟方便了。

 

<think>好的,我现在需要回答用户关于IC设计中锁存器、触发器和寄存器的区别,要求从门电路级和代码级详细解释。首先,我得回忆一下这些组件的基本概念,确保自己理解正确。 锁存器(Latch)和触发器(Flip-Flop)都是用于存储数据的电路,而寄存器(Register)通常由多个触发器组成。用户可能想了解它们的结构、工作原理以及在实际设计中的应用差异。 先从门电路级开始。锁存器通常是电平触发的,比如当使能信号为高电平时,输出会随输入变化;而触发器是边沿触发的,只在时钟信号的上升沿或下降沿捕获输入。常见的锁存器结构有SR锁存器、D锁存器,而触发器则有D触发器、JK触发器等。寄存器一般由多个触发器组成,每个触发器存储一个位,所以一个8位寄存器会有8个D触发器。 代码级方面,锁存器通常在Verilog中由于不完整的条件语句生成,比如在组合逻辑中没有正确覆盖所有分支,导致综合工具推断出锁存器。而触发器则是通过边沿触发的always块来描述的,比如always @(posedge clk)。寄存器则通常用触发器数组来实现,比如用reg声明多个位。 接下来需要确认这些信息的准确性,比如锁存器的电平触发特性是否会导致透明性问题,而触发器的边沿触发如何确保数据的稳定。在代码级,锁存器的无意识生成可能导致设计问题,所以需要避免意外的锁存器,而触发器是设计中的常用存储元件。 可能还需要比较它们的应用场景:锁存器在异步设计中用得较多,但由于对电平敏感,容易引起时序问题;触发器同步于时钟边沿,更可靠,适合同步设计;寄存器作为多位触发器,用于数据存储和传输。 需要检查是否有混淆概念的地方,比如是否寄存器仅仅是触发器的集合,或者是否有其他结构。同时,要确保代码示例正确,比如锁存器的代码可能导致不期望的锁存,触发器的代码正确使用posedge,而寄存器如何声明。 最后,组织这些内容,分点解释每个组件,门电路结构、代码实现,以及它们的区别和用途。确保语言清晰,结构合理,用户能逐步理解三者的不同。</think>在IC设计中,锁存器(Latch)、触发器(Flip-Flop)和寄存器(Register)是存储电路的核心单元。以下从门电路级和代码级进行详细对比: --- ### **一、锁存器(Latch)** #### **门电路级** 1. **电平触发**:锁存器在使能信号(Enable)有效电平期间透明传输数据。 - **D锁存器结构**:由两个交叉耦合的与非门(NOR)或或非门(NAND)构成。 - **工作逻辑**: - 当使能信号为高时:输出$$Q$$跟随输入$$D$$变化; - 当使能信号为低时:输出保持最后一个状态。 - **时序问题**:电平敏感特性可能导致竞争冒险(Race Condition)。 #### **代码级(Verilog)** 锁存器通常因不完整的条件语句被综合工具推断生成: ```verilog always @(enable or d) begin if (enable) q = d; // 缺少else分支导致锁存器 end ``` **注意**:锁存器在同步设计中需谨慎使用,易导致时序违例。 --- ### **二、触发器(Flip-Flop)** #### **门电路级** 1. **边沿触发**:仅在时钟上升沿(或下降沿)捕获输入。 - **D触发器结构**:主从结构(两个锁存器级联),例如用传输门(Transmission Gate)实现。 - **工作逻辑**: - 时钟上升沿到来时:输入$$D$$被采样到输出$$Q$$; - 其他时间:输出保持稳定。 - **抗干扰性**:边沿触发避免了锁存器的透明性问题。 #### **代码级(Verilog)** 通过边沿敏感的always块显式描述: ```verilog always @(posedge clk) begin q <= d; // 非阻塞赋值,代表触发器 end ``` --- ### **三、寄存器(Register)** #### **门电路级** 1. **多位触发器组**:寄存器由多个触发器(通常为D触发器)并行组成。 - **位宽扩展**:例如,8位寄存器由8个独立的D触发器构成; - **同步控制**:共用同一时钟和复位信号,实现多位数据同步存储。 #### **代码级(Verilog)** 直接声明位宽即可实现寄存器: ```verilog reg [7:0] data_reg; // 8位寄存器 always @(posedge clk) begin data_reg <= data_in; // 每个bit对应一个D触发器 end ``` --- ### **四、关键区别对比** | **特性** | **锁存器** | **触发器** | **寄存器** | |-----------------|--------------------------|--------------------------|----------------------| | **触发方式** | 电平触发(Enable信号) | 边沿触发(时钟沿) | 边沿触发(时钟沿) | | **时序可靠性** | 低(易受毛刺影响) | 高(抗干扰性强) | 高 | | **代码特征** | 组合逻辑中缺失条件分支 | 边沿敏感的always块 | 多位触发器组声明 | | **应用场景** | 异步设计、时钟门控 | 同步时序电路 | 数据暂存、总线传输 | --- ### **五、设计实践建议** 1. **避免意外锁存器**:组合逻辑中需覆盖所有条件分支(如添加`else`或`default`)。 2. **触发器优先**:同步设计中使用触发器可简化时序收敛。 3. **寄存器位宽匹配**:确保寄存器位宽与数据通路一致,避免硬件浪费。 通过理解门电路结构和代码实现逻辑,可更精准地控制存储单元的行为,避免设计风险。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值