【常见的语法糖(详解)】

news2025/1/16 20:09:36

在这里插入图片描述

🟩 说几个常见的语法糖

  • 🟢关于语法糖的典型解析
  • 🟢如何解语法糖?
    • 🟢糖块一、switch 支持 String 与枚举
    • 📙糖块二、泛型
    • 📝糖块三、自动装箱与拆箱
    • 🍁糖块四、方法变长参数
    • 🖥️糖块五、枚举
    • ⛳ 糖块六、内部内
    • ✅糖块七、条件编译
    • 🎖️糖块八、断言
    • 😊糖块九、数值字面量
    • 💡糖块十、for-each
    • 📑糖块十一、 try-with-resource
    • 🟢糖块十二、Lambda表达式
      • 🟢Lambda表达式是如何实现的
  • ✅可能遇到的坑
    • 泛型
      • 一、当泛型遇到重载
      • 二、当泛型遇到catch
      • 三、当泛型内包含静态变量
    • 自动装箱与拆箱
      • 对象相等比较
      • 增强for循环
  • 📗总结

🟢关于语法糖的典型解析


语法糖(Syntactic sugar),指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。


虽然Java中有很多语法糖,但是Java虚拟机并不支持这些语法糖,所以这些语法糖在编译阶段就会被还原成简单的基础语法结构,这样才能被虚拟机识别,这个过程就是解语法糖.。


如果看过Java虚拟机的源码,就会发现在编译过程中有一个重要的步骤就是调用desugar0,这个方法就是负责解语法糖的实现。


常见的语法糖有 switch支持枚举及字符里、泛型、条件编译、断言、可变参数、自动装箱/拆箱、枚举、内部类增强for循环、try-with-resources语句、lambda表达式等。


🟢如何解语法糖?


语法糖的存在主要是方便开发人员使用。但其实,Java虚拟机并不支持这些语法糖。这些语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖。


说到编译,大家肯定都知道,Java语言中javac命今可以将后缀名为.java的源文件编译为后缀名为.class的可以运行FJava虚拟机的字节码。如果你去看com.sun.tools.iavac,mainJavaCompiler的源码,你会发现在compile()中有个步骤就是调用desugar(),这个方法就是负责解语法糖的实现的。

🟢糖块一、switch 支持 String 与枚举


前面提到过,从Java 7 开始,Java语言中的语法糖在逐渐富,其中一个比较重要的就是Java 7中 switch 开始支持 String


在开始coding之前先科普下,Java中的 switch 自身原本就支持基本类型。比如 intchar 等。对于 int 类型,直接进行数值的比较。对于 char 类型则是比较其 asckii 码。所以,对于编译器来说, switch 中其实只能使用整型,任何类型的比较都要转换成整型。比如 byteshortchar (asckii码是型)以及 int 。


那么接下来看下 switch 对 string 得支持,有以下代码:


public class SwitchDemostring {
	public static void main(stringl] args) {
		String str = "world";
		case "hello":
			System.out.println("hello");
			break;
		case "world":
			System.out.printn("world");
			break;
		default :
			break;
	}
}

反编译后内容如下:


public class SwitchDemoString {
	public switchDemoString(){
		
	}
	public static void main(String args[]) {
		String str = "world";
		String s;
		switch((s = str).hashCode()) {
			default:
				break;
			case 99162322:
				if(s.equals("hel1o"))
					System.out.println("hello") ;
			break;
			case 113318802:
				if(s.equals("world")) 
					System.out.println("world");
			break;
		}
	}
}

看到这个代码,你知道原来字符串的switch是通过 equals() 和 hashCode() 方法来实现的。还好 hashCode()方法返回的是 int ,而不是 long 。


仔细看下可以发现,进行switch 的实际是哈希值,然后通过使用equals 方法比较进行安全检查,这个检查是必要的,因为哈希可能会发生碰撞。因此它的性能是不如使用枚举进行switch或者使用纯整数常量,但这也不是很差。


📙糖块二、泛型


我们都知道,很多语言都是支持泛型的,但是很多人不知道的是,不同的编译器对于泛型的处理方式是不同的,通常情况下,一个编译器处理泛型有两种方式: Code specialization 和 Code sharing 。C++和C#是使用Code specialization 的处理机制,而Java使用的是 Code sharing 的机制。


Code sharing方式为每个泛型类型创建唯一的字节码表示,并目将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过类型擦除( type erasure )实现的。


也就是说,对于Java虚拟机来说,他根本不认识 MapString,String> map 这样的语法。需要在编译阶段通过类型擦除的方式进行解语法糖


类型擦除的主要过程如下: 1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。 2.移除所有的类理参数。


看如下代码:

Map<StringString> map = new HashMap<String,String>();
map.put("name","hollis");
map.put("wechat""Hollis");
map.put("blog""www.hollischuang.com");

解语法糖之后会变成:


Map map = new HashMap();
map.put("name","hollis");
map.put("wechat""Hollis");
map.put("blog""www.hollischuang.com");

代码如下:

public static <A extends Comparable<A>> A max(Collection<A> xs) {
	Iterator<A> xi = xs.iterator();
	A w = xi.next();
	while (xi .hasNext()) {
		A x = xi.next(l);
		if (w.compareTo(x) < 0)
			w = x;
	}
	return w;
}

类型擦除后会变成:


public static Comparable max(Collection xs) {
	Iterator xi = xs.iterator();
	Comparable w = (Comparable]xi.next();
	while(xi.hasMext()) {
		Comparable x = (Comparable)xi.next();
		if(w.compareTo(x) < 0)
			W=X;
	}
	return w;
}

虚拟机中没有泛型,只有普通类和普通方法,所有泛型类的类型参数在编译时都会被擦除,泛型类并没有自己独有的 class 类对象。比如并不存在 List< String>.class 或是 List< Integer>.class ,而只有 List.class


📝糖块三、自动装箱与拆箱


自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换所以就称作为自动装箱和拆箱。原始类型byte, short,char, int, long,float, double 和 boolean 对应的封装类为Byte, Short, Character, Integer, Long, Float, Double,Boolean。


先来看个自动装箱的代码:


public static void main(Stringl] args) {
	int i = 10;
	Integer n = i;
}

反编译后代码如下:


public static void main(string args[]) {
	int i = 10;
	Integer n = Integer.valueOf(i);
}

再来看个自动拆箱的代码:


public static void main(stringli args) {

	Integer i = 10;
	int n = i;
}

反编译后代码如下


public static void main(String args[]) {
	Integer i = Integer.value0f(1);
	int n = i.intValue( );
}

队反编译得到内容可以看出,在装箱的时候自动调用的是 Integer 的 valueof(int) 方法。而在拆箱的时候自动调用的是 Integer 的 intValue 方法。


所以,装箱过程是通过调用包装器的value0f方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。


🍁糖块四、方法变长参数


可变参数( variable arguments )是在Java 1.5中引入的一个特性。它允许一个方法把任意数量的值作为参数。


看下以下可变参数代码,其中print方法接收可变参数:


public static void main(String[] args) {
	print("Java""公众号:昕宝爸爸爱编程""博客: https://blog.csdn.net/Java_Yangxiaoyuan?spm=1000.2115.3001.5343""QQ: 2439667691");
}

public static void print(string... strs) {
	for (int i = 0; i < strs.length; i++) {
		System.out.println(strs[i]);
	}
}

反编译后代码:


public static void main(string args[]) {
	print(new String[] ("Java""\u516C\u4F17\u53F7:Java_yangxiaoyuan""https://blog.csdn.net/Java_Yangxiaoyuan?spm=1000.2115.3001.5343""QQ\uFF1A2439667691"
});
}

public static transient void print(String strs[]) {
	for(int i = 0; i < strs.length; i++){
		System.out.println(strs[i]);
	}
}

从反编译后代码可以看出,可变参数在被使用的时候,他首先会创建一个数组,数组的长度就是调用该方法是传递的实参的个数,然后再把参数值全部放到这个数组当中,然后再把这个数组作为参数传递到被调用的方法中。


🖥️糖块五、枚举


在Java中,枚举是一种特殊的数据类型,用于表示有限的一组常量。枚举常量是在枚举类型中定义的,每人常量都是该类型的一个实例。Java中的枚举类型是一种安全而优雅的方式来表示有限的一组值。


要想看源码,首先得有一个类吧,那么枚举类型到底是什么类呢? 是 enum 吗? 答案很明显不是,enum class 一样,只是一个关键字,他并不是一个类,那么枚举是由什么类维护的呢,我们简单的写一个枚举:


public enum t {
	SPRING,SUMMER}

然后我们使用反编译,看看这段代码到底是怎么实现的,反编译后代码内容如下:


public final class T extends Enum {
	private T(String s, int i) {
		super(s, i);
	}

	public static T[] values() {
		T at[];
		int i;
		T at1[];
		System.arraycopy(at = ENUMSVALUES, 0, at1 = new T[i = at.length], 0, i);
		return at1;
	}

	public static T valueOf(String s) {
		return (T)Enum.valueOf(demo/T,s);
		
	}

	public static final T SPRING;
	public static final T SUMMER;
	private static final T ENUMSVALUES[];

	//静态代码块
	static {
		SPRING = new T("SPRING"0);
		SUMMER = new T("SUMMER"1);
		ENUM$VALUES = (new TI] {
			SPRINGSUMMER
		});
	}
}

通过反编译后代码我们可以看到, public final class T extends Enum,说明,该类是然承了 Enum 类的,同时 final 关键字告诉我们,这个类也是不能被继承的。


当我们使用 enum 来定义一个枚举类型的时候.编译器会自动帮我们创建一个 final 类型的类继承 Enum 类,所以枚举类型不能被继承。


⛳ 糖块六、内部内


内部类又称为嵌套类,可以把内部类理解为外部类的一个普通成员。


内部类之所以也是语法糖,是因为它仅仅是一个编译时的概念, outer.java 里面定义了一个内部类 inner。
一旦编译成功,就会生成两个完全不同的 .class 文件了,分别是 outer.class 和 outer$inner.class 。


public class OutterClass {
	private String userName;

	public String getUserName()  {
		return userName ;
	}
	
	public void setUserName(String userName) {
		this .userName = userName;
	}
	
	public static void main(String[] args) {
		
	}

	class InnerClass {
		private String name;
		
		public String getName() {
			return name;
		}
	
		public void setName(String name) {
			this .name = name ;
		}
	}
}

以上代码编译后会生成两个class文件: 0utterClass$InnerClass.class、OutterClass.class 。


当我们尝试对 0utterClass.class 文件进行反编译的时候,命今行会打印以下内容:



Parsing 0utterClass.class…Parsing inner class 0utterClass$InnerClass.class… Generating 0utterClass.jad 。

他会两个文件全部进行反编译,然后 起生成一个 utterclass.jad 文件。文件内容如下:


public class OutterClass {
	class InnerClass {
		public String getName() {
			return name ;
		}
		public void setName(String name) {
			this .name = name ;
		}
		private String name;
		final OutterClass this$0;
		
		InnerClass() {
			this.this$0 = OutterClass.this;
			super();
		}
	}
	public OutterClass() {}
	public String getUserName( ) {
		return userName;
	}
	public void setUserName(String userName) {
		
		this .userName = userName;
	}
	
	public static void main(String args1[]) {}
	private String userName ;
}

✅糖块七、条件编译


解析:
一般情况下,程序中的每一行代码都要参加编译。但有时候出于对程序代码优化的考虑,希望只对其中一部分内容进行编译,此时就需要在程序中加上条件,让编译器只对满足条件的代码进行编译,将不满足条件的代码舍弃,这就是条件编译。


如在C或CPP中,可以通过预处理语句来实现条件编译。其实在Java中也可实现条件编译。我们先来看一段代码:
public class ConditionalCompilation {
	public static void main(String[] args) {
		final boolean DEBUG = true;
		if(DEBUG)
			System.out.println("Hello,DEBUG!");
	

		final boolean ONLINE = false;
		if (ONLINE) 
			System.out.printIn("Hello,ONLINE!");
    }
}

反编译后代码如下:


public class ConditionalCompilation {
	public ConditionalCompilation() {}

	public static void main(String args[]) {
		boolean DEBUG = true;
		System.out.println("He110,DEBUG!");
		boolean ONLINE = false;
	}
}

首先,我们发现,在反编译后的代码中没有 System.out.println(“Hello,ONLINE!”);,这其实就是条件编译。当 if(ONLINE) 为false的时候,编译器就没有对其内的代码进行编译。


所以,Java语法的条件编译,是通过判断条件为常量的i语句实现的。其原理也是Java语言的语法糖。根据if判断条件的真假,编译器直接把分支为false的代码块消除。通过该方式实现的条件编译,必须在方法体内实现,而无法在正整个Java类的结构或者类的属性上进行条件编译,这与C/C++的条件编译相比,确实更有局限性。在Java语言设计之初并没有引入条件编译的功能,虽有局限,但是总比没有更强。


🎖️糖块八、断言


在Java中, assert 关键字是从AVA SE 1.4引入的,为了避免和老版本的Java代码中使用了 assert 关键字导致错误,Java在执行的时候默认是不启动断言检查的 (这个时候,所有的断言语句都将忽略!),如果要开启断言检查,则需要用开关 -enableassertions 或-ea 来开启。

看一段包含断言的代码:


public class AssertTest  {
	public static void main(String args[]) {
		int a = 1;
		int b = 1;
		assert a == b;
		System.out.println("公众号: Java");
		assert a != b :"Java";
		System.out.printIn("博客: https://blog.csdn.net/Java_Yangxiaoyuan");
	}
}

反编译之后:


public class AssertTest {
	public AssertTest() {}
	public static void main(String args[]) {
		int a = 1;
		int b = 1;
		if(!$assertionsDisabled && a != b)
			throw new AssertionError();
		System.out.println(" u516C u4F17\u53F7\UFF1AJava");
		if(!$assertionsDisabled && a == b){
			throw new AssertionError("Hollis");
		}else{
			System.out.println(" u535A\u5BA2\UFF1Ahttps://blog.csdn.net/Java_Yangxiaoyuan");
			return;
		}
			
	}

	static final boolean $assertionsDisabled = !com/hollis/suguar/AssertTest.desiredAssertionStatus();
}

很明显,反编译之后的代码要比我们自己的代码复杂的多。所以,使用了assert这个语法糖我们节省了很多代码.其实断言的底层实现就是if语言,如果断言结果为true,则什么都不做,程序继续执行,如果断言结果为false.则程序抛出AssertError来打断程序的执行。-enableassertions 会设置SassertionsDisabled字段的值。


😊糖块九、数值字面量


在iava 7中,数值字面量,不管是整数还是浮点数,都允许在数字之间插入任意多人下划线。这些下划线不会对字面量的数值产生影响,目的就是方便阅读。


比如:


public class Test  {
	public static void main(String... args)  {
		int i = 10_000;
		System.out.println(i);
	}
}

反编译后:

public class Test {
	public static void main(string[] args) {
		int i = 10000:
		System.out.println(i);
	}
}

反编译后就是把 _ 删除了。也就是说 编译器并不认识在数值字面量中的 _ ,需要在编译阶段把它去掉。


💡糖块十、for-each


增强for循环( for-each )相信大家都不陌生,日常开发经常会用到的,他会比for循环要少写很多代码,那么这个语法糖背后是如何实现的呢?


public static void main(string... args)  {
	String[] strs =["Hollis""公众号: Hollis""博客: www.hollischuang.com"};
	for (String s : strs) {
		System.out.println(s);
	}
	List<String> strList = Immutablelist.of("Java","公众号: Java","博客: https://blog.csdn.net/Java_Yangxiaoyuan");
	for (String s : strList) {
		System.out.println(s);
	}
}

反编译之后:


public static transient void main(String args[]) {
	String strs[] = {
		"Java""\u516C\u4F17\u53F7:Java_yangxiaoyuan""https://blog.csdn.net/Java_Yangxiaoyuan?spm=1000.2115.3001.5343""QQ\uFF1A2439667691"
	};
	String args1[] = strs;
	int i = args1.length;
	for(int j = 0; j < i; j++) {
		String s = args1[j];
		System.out.println(s);
	}
	List strList = ImmutableList.of("Java""\u516C\u4F17\u53F7:Java_yangxiaoyuan""https://blog.csdn.net/Java_Yangxiaoyuan?spm=1000.2115.3001.5343""QQ\uFF1A2439667691");
	String s;
	for(Iterator iterator = strlist.iterator(); iterator.haslext(); System.out.println(s)) {
		s = (String)iterator.next();
	}
}

代码很简单,for-each的实现原理其实就是使用了普通的for循环和迭代器


📑糖块十一、 try-with-resource


Java里,对于文件操作作l0流、数据库连接等开销非常昂贵的资源,用完之后必须及时通过close方法将其关闭,否则资源会一直处于打开状态,可能会导致内存泄露等问题。


关闭资源的常用方式就是在 finally 块里是释放,即调用 close 方法。比如,我们经常会写这样的代码:


public static void main(Stringl] args) {
	BufferedReader br = nul1;
	try {
		String line;
		
		br = new BufferedReader(new FileReader("d:  hollischuang.xml"));
	
		while ((line = br.readLine()) != nul1) {
		System.out.println(line);
		}
	}catch  (IOException e) {
		// handle exception
	} finally {
		try {
			if (br != nul1) {
				br.close();
			}
		}catch  (IOException ex)  {
			// handle exception
		}
	}
}

从Java 7开始,idk提供了一种更好的方式关闭资源,使用 try-with-resources 语句,改写一下上面的代码效果如下:


public static void main(String... args) {
	try (BufferedReader br = new BufferedReader(new FileReader("d: ) hollischuang.xml"))) {
		String line;
		while ((line = br .readLine( )) != nul1) {
			System.out.println(line);
		}
	} catch (IOException e) {
		// handle exception
	}
}

看,这简直是一大福音啊,虽然我之前一般使用 IOUtils 去关闭流,并不会使用在 finally 中写很多代码的方式,但是这种新的语法糖看上去好像优雅很多呢。看下他的背后:


public static transient void main(String args[]) {
	BufferedReader br;
	Throwable throwable;
	br = new BufferedReader(new FileReader("d:   hollischuang.xml"));
	throwable = null;
	String line;

	try {
		while((line = br.readline()) != nul1);
		System.out.println(line);
	} catch(Throwable throwable2) {
		throwable = throwable2 ;
		throw throwable2;
	}
	if(br != null) {
		if(throwable != nul1) {
			try {
				br.close();
			} catch(Throwable throwable1) {
				throwable.addSuppressed(throwable1);
			}
		}
		else {
			br.close();
		}
		break MISSING_BLOCK_LABEL_113;
		Exception exception;
		exception;
		if(br != null) 
			if(throwable != null)
				try {
					br.close();
				} catch(Throwable throwable3) {
					throwable.addSuppressed(throwable3);
				}
			else 
				br.close();	
		throw exception;
		IOException ioexception;
		ioexception;
	}
}


其实背后的原理也很简单,那些我们没有做的关闭资源的操作,编译器都帮我们做了。所以,再次印证了,语法糖的作用就是方便程序员的使用,但最终还是要转成编译器认识的语言。


🟢糖块十二、Lambda表达式

🟢Lambda表达式是如何实现的


关于lambda表达式,有人可能会有质疑,因为网上有人说他并不是语法糖。其实我想纠正下这个说法。


Labmda表达式不是匿名内部类的语法糖,但是他也是一个语法糖。实现方式其实是依赖了几个JVM底层提供的lambda相关api。


先来看一个简单的lanbda表达式。遍历一个List:


import java.util.Arrays;  
import java.util.List;  
import java.util.stream.Collectors;  
  
public class LambdaComplexExample {  
    public static void main(String[] args) {  
        // 创建一个List集合  
        List<String> list = Arrays.asList("Apple", "Banana", "Cherry", "Date", "Elderberry");  
  
        // 使用Lambda表达式和Stream API处理集合  
        List<String> filteredList = list.stream()  
                .filter(s -> s.startsWith("A")) // 过滤以"A"开头的元素  
                .map(String::toUpperCase) // 将元素转换为大写  
                .collect(Collectors.toList()); // 收集结果到新的List集合  
  
        // 输出结果  
        filteredList.forEach(System.out::println);  
    }  
}

使用Stream API来处理集合。首先,我们使用filter方法来过滤出所有以"A"开头的元素。然后,我们使用map方法将每个元素转换为大写形式。最后,我们使用collect方法将结果收集到一个新的List集合中。在最后,我们使用forEach方法来打印出结果集中的每个元素。


总结,Lambda 表达式的实现其实是依赖了一些底层的API,在编译阶段,编译器会把Lambda表达式进行解糖,转换成调用内部api的方式。

✅可能遇到的坑

泛型

一、当泛型遇到重载


public class GenericTypes {
	public static void method(List<String> ist)  {
		System.out.println("invoke method(List<String> list)");
	}

	public static void method(List<Integer> list)  {
		System.out.println("invoke method(List<Integer> list)");
	}
}

上面这段代码,有两个重载的函数,因为他们的参数类型不同,一个是List< String> 另一个是List< lnteger>但是,这段代码是编译通不过的。因为我们前面讲过,参数List< String>和List< lnteger>编译之后都被擦除了变成了一样的原生类型List,擦除动作导致这两个方法的特征签名变得一模一样。


二、当泛型遇到catch


泛型的类型参数不能用在Java是常外理的catch语句中。因为是学外理是电JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型 MyException< String>和 MyException< Integer>的


三、当泛型内包含静态变量


public class StaticTest {
	public static void main(stringl] args) {
		GT<Integer> gti = new GT<Integer>();
		gti.var=1;
		GT<String> gts = new GT<String>();
		gts.var=2;
		System.out.println(gti.var);
	}
}

class GT<T> {
	public static int var=0;
	public void nothing(T x){

	}
}

以上代码输出结果为: 2! 由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。

自动装箱与拆箱

对象相等比较


public static void main(string[] args) {
	Integer a = 1000;
	Integer b = 1000;
	Integer c = 100;
	Integer d = 100;
	System.out.println("a == b is " + (a == b));
	System.out.println(("c == d is " + (c == d)));
}

输出结果:

在这里插入图片描述

在ava 5中,在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。


适用于整数值区间-128至+127.




只适用于自动装箱。使用构造函数创建对象不适用。


增强for循环


ConcurrentModificationException


for (Student stu : students) {
	if (stu.getId() == 2) 
		students.remove( stu) ;
}

会抛出 ConcurrentModificationException 异常。


lterator是工作在一个独立的线程中,并且拥有一个 mutex 锁。lterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要跌代的对象,所以按照 fail-fast 原则 lterator 会马上抛出 java.util.ConcurrentModificationException 异常。


所以 Iterator 在工作的时候是不允许被跌代的对象被改变的。但你可以使用 iterator 本身的方法 remove()来删除对象,Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。


📗总结


本博文介绍了12种Java中常用的语法糖。所冒语法糖就是提供给开发人员便于开发的一种语法而已。但是这种语法只有开发人员认识。要想被执行,需要进行解糖,即转成JVM认识的语法。当我们把语法糖解糖之后,你就会发现其实我们日常使用的这些方便的语法,其实都是一些其他更简单的语法构成的。


有了这些语法糖,我们在日常开发的时候可以大大提升效率,但是同时也要避免过渡使用。使用之前最好了解下原理,避免掉坑。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1331512.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux多线程:POSIX信号量,基于信号量的环形队列实现生产者消费者模型

目录 一、POSIX信号量1.1 初始化信号量1.2 销毁信号量1.3 等待信号量1.4 发布信号量1.5 基于环形队列的生产消费模型(用信号量控制生产者和消费者之间的同步互斥关系)1.5.1 makefile1.5.2 RingQueue.hpp1.5.3 Sem.hpp1.5.4 Task.hpp1.5.5 main.cc 二、信号量控制的环形队列原理…

.Net 访问电子邮箱-LumiSoft.Net,好用

序言&#xff1a; 网上找了很多关于.Net如何访问电子邮箱的方法&#xff0c;但是大多数都达不到想要的需求&#xff0c;只有一些 收发邮件。因此 花了很大功夫去看 LumiSoft.Net.dll 的源码&#xff0c;总算做出自己想要的结果了&#xff0c;果然学习诗人进步。 介绍&#xff…

Qt 开源项目

Qt 开源项目 Omniverse View链接技术介绍 QuickQanava链接技术介绍QField链接技术介绍 AtomicDEX链接技术介绍 Status-desktop链接技术介绍 Librum链接技术介绍 A Simple Cross-Platform ReaderQPrompt链接技术介绍 GCompris链接技术介绍 Scrite链接技术介绍 QSkinny链接技术介…

如何在PC上运行大模型

如何在PC上运行大模型 在PC上使用CPU运行大模型不如使用GPU高效&#xff0c;但仍然是可以实现的大模型推理。 大模型训练要求的资源更高&#xff0c;这里直接使用面向开源的Facebook’s LLaMA model(llama-2-7b-chat.Q2_K.gguf)。 连接CPU与LLaMA model的是llama.cpp。 为方便…

认识Linux背景

1.发展史 Linux从哪里来&#xff1f;它是怎么发展的&#xff1f;在这里简要介绍Linux的发展史 要说Linux&#xff0c;还得从UNIX说起 UNIX发展的历史 1968年&#xff0c;一些来自通用电器公司、贝尔实验室和麻省理工学院的研究人员开发了一个名叫Multics的特殊操作系统。Mu…

LLaMA开源大模型源码分析!

Datawhale干货 作者&#xff1a;宋志学&#xff0c;Datawhale成员 花了一晚上照着transformers仓库的LLaMA源码&#xff0c;把张量并行和梯度保存的代码删掉&#xff0c;只留下模型基础结构&#xff0c;梳理了一遍LLaMA的模型结构。 今年四月份的时候&#xff0c;我第一次接触…

第一次记录QPSK,BSPK,MPSK,QAM—MATLAB实现

最近有偶然的机会学习了一次QPSK防止以后忘记又得找资料&#xff0c;这里就详细的记录一下 基于 QPSK 的通信系统如图 1 所示&#xff0c;QPSK 调制是目前最常用的一种卫星数字和数 字集群信号调制方式&#xff0c;它具有较高的频谱利用率、较强的抗干扰性、在电路上实现也较为…

基于STM32单片机模拟智能电梯步进电机控制升降毕业设计3

STM32单片机模拟智能电梯步进电机控制数码管显示3 演示视频&#xff08;复制到浏览器打开&#xff09;&#xff1a; 基于STM32单片机的智能电梯控制系统模拟智能电梯步进电机控制系统设计数码管显示楼层设计/DIY开发板套件3 产品功能描述&#xff1a; 本系统由STM32F103C8T6单…

技术交底二维码的应用

二维码技术交底可以逐级落实、责任到人、有据可查、是目前最方便、实用的交底方式&#xff0c;下面我们讲解技术交底二维码的应用。 1、生成对应的技术交底二维码&#xff0c;将施工方案、技术资料、安全教育资料等内容上传到二维码里。打印出来现场粘贴&#xff0c;便于作业班…

(一)深入理解Mysql底层数据结构和算法

什么是索引 索引是帮助MySQL高效获取数据的排好序的数据结构 数据结构有哪些 数据结构模拟网站&#xff1a;Data Structure Visualization 二叉树 不适合做自增ID的数据结构。如下示意图&#xff0c;假设采用二叉树作为表自增主键ID的数据存储结果如下&#xff1a;当查询i…

行列式:方程组未知数的计算:克拉默法则

行列式&#xff1a;方程组未知数的计算 ![ ](https://img-blog.csdnimg.cn/direct/4a9c2800da3746ea95c1a3c93057d796.png)

VS Code实现“Ctr+save”保存代码自动格式化

一、下载Prettier - Code formatter插件 点击安装即可 二、配置 【1】打开文件——首选项——设置 或者左下角齿轮打开设置 【2】搜索设置框输入editor default formatter&#xff08;意思是默认格式化设置&#xff09;&#xff0c;接着下拉选中刚下好的插件名称Prettier - C…

【Vulnhub 靶场】【Corrosion: 1】【简单】【20210731】

1、环境介绍 靶场介绍&#xff1a;https://www.vulnhub.com/entry/corrosion-1,730/ 靶场下载&#xff1a;https://download.vulnhub.com/corrosion/Corrosion.ova 靶场难度&#xff1a;简单 发布日期&#xff1a;2021年07月31日 文件大小&#xff1a;7.8 GB 靶场作者&#xf…

Windows安装cnpm报错 The operation was rejected by your operating system.

Windows在安装cnpm时出现如下错误 npm ERR! The operation was rejected by your operating system. npm ERR! Its possible that the file was already in use (by a text editor or antivirus), npm ERR! or that you lack permissions to access it. npm ERR! npm ERR! If y…

[vue]Echart使用手册

[vue]Echart使用手册 使用环境Echart的使用Echart所有组件和图表类型Echart 使用方法 使用环境 之前是在JQuery阶段使用Echart&#xff0c;直接引入Echart的js文件即可&#xff0c;现在是在vue中使用,不仅仅时echarts包&#xff0c;还需要安装vue-echarts&#xff1a; "…

智能优化算法应用:基于鹈鹕算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于鹈鹕算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于鹈鹕算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.鹈鹕算法4.实验参数设定5.算法结果6.参考文献7.MA…

C语言——小细节和小知识6

一、转义字符相关 \ 反斜杠&#xff0c;转义字符中的转义序列符 \? 将?转义&#xff0c;防止他被识别成三字母词(很早的东西)中的问号 //三字母词 //??(是[ //??)是] printf("%s","??(??)"); //打印结果是[] 二、fopen函数fc…

Linux 基础指令三

一、cat命令 默认是顺序查看&#xff0c;可同时查看多个文件&#xff0c;只能看普通文件&#xff0c;不能看文件以外 使用格式: cat [选项] 文件名 常用选项 -n显示行号-b跳过空白行编号-s将所有的连续的多个空行替换为一个空行&#xff08;压缩成一个空行&#xff0…

适配器模式学习

适配器模式&#xff08;Adapter&#xff09;将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 适配器模式分为类适配器模式和对象适配器模式两种&#xff0c;前者类之间的耦合度比后者高&#xff0c;且要…

在Linux安装卸载文件

目录 一、Linux系统应用程序 1.典型的应用程序的目录结构 2、常见的软件包封装类型 二、RPM软件包管理 1、RPM是什么&#xff1f; 2、rpm一般命名格式 3、RPM安装包从何而来&#xff1f;如何挂载&#xff1f; 4、挂载的注意事项: 5、目的&#xff1a;提供安装包 6、查…