学习互联网架构第十一课(并发类容器之Queue)

本文详细介绍了JDK中并发队列的两种实现:ConcurrentLinkedQueue和BlockingQueue。ConcurrentLinkedQueue适用于高并发场景,通过无锁机制实现高性能;BlockingQueue包括ArrayBlockingQueue、LinkedBlockingQueue等,提供阻塞特性以控制生产者消费者模型。

        在并发队列上JDK提供了两套实现,一个是以ConcurrentLinkedQueue为代表的高性能队列,一个是以BlockingQueue接口为代表的阻塞队列,无论哪种都继承自Queue。如下图所示。

        

           首先我们来学ConcurrentLinkedQueue,ConcurrentLinkedQueue:是一个适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能,通常ConcurrentLinkedQueue性能好于BlockingQueue。它是一个基于链接节点的无界线程安全队列。该队列的元素遵循先进先出的原则。头是最先加入的,尾是最近加入的,该队列不允许null元素。
        ConcurrentLinkedQueue重要方法:
        add()和offer()都是加入元素的方法(在ConcurrentLinkedQueue中,这两个方法没有任何区别,大家可能有疑问,既然两个没有区别为何还要弄两个方法,这是因为这两个方法都继承自父类Queue,在其它场景下是可能不一样的)
        poll()和peek()都是取头元素节点,区别在于前者会删除元素,后者不会。
        下面我们来看个例子,如下所示。这是最常用的几个方法。

package com.internet.queue;

import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;

public class UseQueue {
	public static void main(String[] args) {
		//高性能无阻塞无界队列:ConcurrentLinkedQueue
		ConcurrentLinkedQueue<String> concurrentLinkedQueue = new ConcurrentLinkedQueue<String>();
		concurrentLinkedQueue.offer("a");
		concurrentLinkedQueue.add("b");
		concurrentLinkedQueue.offer("c");
		concurrentLinkedQueue.add("d");
		
		System.out.println(concurrentLinkedQueue.poll());//取出第一个元素并删除
		System.out.println(concurrentLinkedQueue.size());//打印队列的大小
		System.out.println(concurrentLinkedQueue.peek());//取出第一个元素,不删除
		System.out.println(concurrentLinkedQueue.size());//打印队列的大小
		System.out.println("--------------------------------------------------------");
		for (Iterator iterator = concurrentLinkedQueue.iterator(); iterator.hasNext();) {
			String str = (String) iterator.next();
			System.out.println(str);
		}
	}
}
        上面运行结果如下所示。

a
3
b
3
--------------------------------------------------------
b
c
d
          下面我们来验证ConcurrentLinkedQueue是线程安全的,我们向队列里添加一个元素,然后用多个线程去获取队列中的这个元素,如下所示。

package com.internet.queue;

import java.util.concurrent.ConcurrentLinkedQueue;

public class UseQueue {
	public static void main(String[] args) {
		//高性能无阻塞无界队列:ConcurrentLinkedQueue
		ConcurrentLinkedQueue<String> concurrentLinkedQueue = new ConcurrentLinkedQueue<String>();
		concurrentLinkedQueue.offer("a");
		
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
			   //不要使用.size()方法,因为那样效率非常低
			   if(!concurrentLinkedQueue.isEmpty()){
				   System.out.println("进入线程1");
				   String str = concurrentLinkedQueue.poll();
				   System.out.println("线程1取出的元素:"+str);
			   }
			}
		},"t1");
        Thread t2 = new Thread(new Runnable() {
			
			@Override
			public void run() {
			   //不要使用.size()方法,因为那样效率非常低
			   if(!concurrentLinkedQueue.isEmpty()){
				   System.out.println("进入线程2");
				   String str = concurrentLinkedQueue.poll();
				   System.out.println("线程2取出的元素:"+str);
			   }
			}
		},"t2");
        Thread t3 = new Thread(new Runnable() {
			
			@Override
			public void run() {
			   //不要使用.size()方法,因为那样效率非常低
			   if(!concurrentLinkedQueue.isEmpty()){
				   System.out.println("进入线程3");
				   String str = concurrentLinkedQueue.poll();
				   System.out.println("线程3取出的元素:"+str);
			   }
			}
		},"t3");
        Thread t4 = new Thread(new Runnable() {
			
			@Override
			public void run() {
			   //不要使用.size()方法,因为那样效率非常低
			   if(!concurrentLinkedQueue.isEmpty()){
				   System.out.println("进入线程4");
				   String str = concurrentLinkedQueue.poll();
				   System.out.println("线程4取出的元素:"+str);
			   }
			}
		},"t4");
        Thread t5 = new Thread(new Runnable() {
			
			@Override
			public void run() {
			   //不要使用.size()方法,因为那样效率非常低
			   if(!concurrentLinkedQueue.isEmpty()){
				   System.out.println("进入线程5");
				   String str = concurrentLinkedQueue.poll();
				   System.out.println("线程5取出的元素:"+str);
			   }
			}
		},"t5");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
	}
}
         其中一次运行结果如下所示,可以看到,能取出元素的只有一个线程,无论执行多少次,都只有一个线程能够获取到元素a,其它线程获取的都是null。注意判断队列是否为空时,不要使用.size()方法,因为.size() 是要遍历一遍集合的,因此比较慢,使用isEmpty()效率比较高。

进入线程1
进入线程4
进入线程3
进入线程2
线程3取出的元素:null
线程2取出的元素:null
线程4取出的元素:null
线程1取出的元素:a
        下面来学习下常见的几个阻塞队列,由于底层源码都比较难懂,我这里还是只说用法,想深入研究的同学可以去查看源码。
第一个:ArrayBlockingQueue
         基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,其内部没实现读写分离,也就意味着生产和消费不能完全并行,长度是需要定义的,可以指定先进先出或者先进后出,也叫有界队列,在很多场合非常适用。
         之所以说ArrayBlockingQueue是有界队列,是因为我们在使用该队列时必须指定队列的容量大小,如下图所示,三种实例化方式都必须有"int capacity"(容量大小)。

         ArrayBlockingQueue向队列添加元素有三种方法,分别是put、add、offer。这三个方法虽然都是添加元素,但是作用却不同。首先我们来看下put方法,如下所示,我们给队列设置容量为5,然后故意向容器中添加6个元素,看是什么效果。

package com.internet.queue;

import java.util.concurrent.ArrayBlockingQueue;

public class UseQueue {
	public static void main(String[] args) throws Exception {
		ArrayBlockingQueue<String> array = new ArrayBlockingQueue<>(5);
		array.put("a");
		array.put("b");
		array.put("c");
		array.put("d");
		array.put("e");
		array.put("f");
	}
}
         上面代码执行效果如下图,可以看到,线程一直处于running状态,这是因为put将指定元素插入到此队列的尾部,如有必要,则等待空间变得可用。现在第六个元素由于插入不到队列当中,它就在这儿等着,什么时候有元素从队列中出去了,它就插入到队列当中。

          下面我们再试试add方法,代码如下所示。

package com.internet.queue;

import java.util.concurrent.ArrayBlockingQueue;

public class UseQueue {
	public static void main(String[] args) throws Exception {
		ArrayBlockingQueue<String> array = new ArrayBlockingQueue<>(5);
		array.add("a");
		array.add("b");
		array.add("c");
		array.add("d");
		array.add("e");
		array.add("f");
	}
}
         运行结果如下图,可以看到抛出了异常,说队列已经满了,盛不下第六个元素了。add方法的作用便是:将指定的元素插入到此队列中(如果立即可行且不会违反容量限制),在成功时返回 true,如果当前没有可用空间,则抛出 IllegalStateException。

          下面我们再看下offer方法,代码如下:

package com.internet.queue;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

public class UseQueue {
	public static void main(String[] args) throws Exception {
		ArrayBlockingQueue<String> array = new ArrayBlockingQueue<>(5);
		array.offer("a");
		array.offer("b");
		array.offer("c");
		array.offer("d");
		array.offer("e");
		System.out.println(array.offer("f",3,TimeUnit.SECONDS));
	}
}
        下面是运行结果,可以看到offer返回的是bool类型的值,offer方法将指定元素插入到此队列的尾部(如果立即可行且不会超出此队列的容量),在成功时返回 true,如果此队列已满,则返回 false。当使用有容量限制的队列时,此方法通常要优于 add 方法,后者可能无法插入元素,而只是抛出一个异常。

 第二个:LinkedBlockingQueue

          举个例子如下:



         代码如下,其中q.drainTo(list,3)是一次性把队列中的三个元素都存放到list当中,返回值是成功从队列中取出的元素个数。我们说LinkedBlockingQueue是无界队列是因为我们可以不设置队列的长度,这样队列便是无界的。

package com.internet.queue;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;

public class UseQueue {
   public static void main(String[] args) {
	  //改队列可以是无界队列也可以是有界队列,不指定长度便是无界队列,指定长度便是有界队列
	  LinkedBlockingQueue<String> q = new LinkedBlockingQueue<String>();
	  q.offer("a");
	  q.offer("b");
	  q.offer("c");
	  q.offer("d");
	  q.offer("e");
	  q.offer("f");
	  List<String> list = new ArrayList<String>();
	  System.out.println(q.drainTo(list,3));
	  System.out.println(list.size());
	  for(String str : list){
		  System.out.println(str);
	  }
   }
}
       运行结果如下:

3
3
a
b
c

       但是如果给LinkedBlockingQueue指定长度的话,它就变成了有界队列,比如我们把LinkedBlockingQueue的长度设置为5,超出队列的话,将无法再添加元素,如下图所示。


          运行结果如下图所示,q.offer()方法如果返回true表示添加成功,返回false表示添加失败。可见第6个元素并没有成功添加。


第三个:SynchronousQueue

         这个队列非常特殊,它不能装任何元素。      

package com.internet.queue;

import java.util.concurrent.SynchronousQueue;

public class UseQueue {
   public static void main(String[] args) {
	  SynchronousQueue<String> q = new SynchronousQueue<>();
	  System.out.println(q.offer("a"));
   }
}
        运行结果如下图所示

        下面看个例子,这个例子,貌似SynchronousQueue可以添加元素,如下所示。但是其实SynchronousQueue依然是没有存储元素的,这里之所以没有报错,是因为我们先启动了一个线程t1要消费SynchronousQueue这个队列中的元素,线程t2要向SynchronousQueue队列添加一个元素,这时候会发生什么呢?这时候,线程t2并不会真的把元素添加到队列中,而是直接将要添加的元素交给线程t1了。也就是说,SynchronousQueue队列还是不会真正存储元素的。

package com.internet.queue;

import java.util.concurrent.SynchronousQueue;

public class UseQueue {
   public static void main(String[] args) {
	  final SynchronousQueue<String> q = new SynchronousQueue<String>();
	  Thread t1 = new Thread(new Runnable() {
		
			@Override
			public void run() {
				try {
					System.out.println(q.take());
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
	   });
	   t1.start();
	   Thread t2 = new Thread(new Runnable() {
		
			@Override
			public void run() {
				q.add("ffasss");
			}
		});
	    t2.start();
   }
}

          肯定有些人会有疑问,既然SynchronousQueue不能装任何元素的话,那么要它有何用?还有就是有界队列和无界队列的应用场景是什么呢?如下图所示。


第四个:PriorityBlockingQueue

         基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定,也就是说传入队列的对象必须实现Comparable接口),在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁,他也是一个无界的队列。下面我们便以一个小例子来说明。

         参与比较的对象必须实现Comparable接口,如下所示,重写了compareTo方法,用id来进行比较。

package com.internet.queue;

public class Task implements Comparable<Task>{
	private int id;
	private String name;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public int compareTo(Task task) {
		return this.id > task.id ? 1 : (this.id < task.id ? -1 : 0);
	}

}
          下面我们使用PriorityBlockingQueue ,如下所示。

package com.internet.queue;

import java.util.Iterator;
import java.util.concurrent.PriorityBlockingQueue;

public class UsePriorityBlockingQueue {
   public static void main(String[] args) {
	  PriorityBlockingQueue<Task> q = new PriorityBlockingQueue<Task>();
	  Task t1 = new Task();
	  t1.setId(3);
	  t1.setName("任务1");
	  Task t2 = new Task();
	  t2.setId(6);
	  t2.setName("任务2");
	  Task t3 = new Task();
	  t3.setId(1);
	  t3.setName("任务3");
	  q.add(t1);
	  q.add(t2);
	  q.add(t3);
	  //添加到队列里面的元素还是没有顺序的
	  for (Iterator iterator = q.iterator(); iterator.hasNext();) {
		Task task = (Task) iterator.next();
		System.out.println(task.getName());
	  }
	  //只有当往外取数据的时候才有顺序
	  try {
		  System.out.println(q.take().getId());
		  System.out.println(q.take().getId());
		  System.out.println(q.take().getId());
	  } catch (InterruptedException e) {
		  e.printStackTrace();
	  }
	  
   }
}
         我们运行main方法,可以看到结果如下所示,可以看到,添加到队列里的对象其实是没有顺序的(任务3对应的对象的id是1,任务2对应的对象的id是6,任务1对应的对象的id是3),而我们往外取的时候可以看到取出的顺序是1、3、6,符合排序规则。

任务3
任务2
任务1
1
3
6

第五个:DelayQueue

        带有延迟时间的Queue,其中的元素只有当其指定的延迟时间到了,才能够从队列中获取该元素。DelayQueue中的元素必须实现Delayed接口,DelayQueue是一个没有大小限制的队列,应用场景很多,比如对缓存超时的数据进行移除、任务超时处理、空闲连接的关闭等等。

        下面我们便来看一个网民在网吧上网的例子,首先我们来新建一个网民类,如下所示

package com.internet.queue;

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * 网民类
 * @author wanghaijie
 *
 */
public class Wangmin implements Delayed{
	//网名
	private String name;
	//身份证号
	private String id;
	//截止时间
	private long endTime;
	//定义时间工具类,以秒为单位
	private TimeUnit timeUnit = TimeUnit.SECONDS;

	public Wangmin(String name,String id,long endTime){
		this.name = name;
		this.id = id;
		this.endTime = endTime;
	}
	
	
	public String getName() {
		return name;
	}



	public void setName(String name) {
		this.name = name;
	}



	public String getId() {
		return id;
	}



	public void setId(String id) {
		this.id = id;
	}



	public long getEndTime() {
		return endTime;
	}



	public void setEndTime(long endTime) {
		this.endTime = endTime;
	}



	@Override
	public int compareTo(Delayed delayed) {
		Wangmin w = (Wangmin)delayed;
		return this.getDelay(this.timeUnit) - w.getDelay(this.timeUnit) > 0 ? 1:0;
	}

	@Override
	public long getDelay(TimeUnit unit) {
		return unit.convert(endTime, TimeUnit.MILLISECONDS) - unit.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS);
	}
 
}
         下面我们便来使用DelayQueue,如下所示

package com.internet.queue;

import java.util.concurrent.DelayQueue;

public class WangBa implements Runnable{
	//延迟队列
	private DelayQueue<Wangmin> queue = new DelayQueue<>();
	//是否营业的标志
	public boolean yingye = true;

	//上机方法,为了测试方便,规定交1块钱只能上1秒网。
	public void shangji(String name, String id, int money){
		//第三个参数是下机时间,上网时长加上当前时间就是下机时间
		Wangmin man = new Wangmin(name, id, 1000*money + System.currentTimeMillis());
		System.out.println("网名"+man.getName()+" 身份证"+man.getId()+" 交钱"+money+"块,开始上机...");
		this.queue.add(man);
	}
	
	public void xiaji(Wangmin man){
		System.out.println("网名"+man.getName()+" 身份证"+man.getId()+"时间到下机...");
	}
	
	@Override
	public void run() {
		while(yingye){
			try {
				Wangmin man = queue.take();
				xiaji(man);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args){
		try {
			System.out.println("网吧开始营业");
			WangBa wangBa = new WangBa();
			Thread shangwang = new Thread(wangBa);
			shangwang.start();
			
			wangBa.shangji("路人甲", "123", 1);
			wangBa.shangji("路人乙", "234", 10);
			wangBa.shangji("路人丙", "345", 5);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
     
}
         运行main方法,结果如下所示,可见,DelayQueue在处理网吧上网的问题上还是非常方便的。

网吧开始营业
网名路人甲 身份证123 交钱1块,开始上机...
网名路人乙 身份证234 交钱10块,开始上机...
网名路人丙 身份证345 交钱5块,开始上机...
网名路人甲 身份证123时间到下机...
网名路人丙 身份证345时间到下机...
网名路人乙 身份证234时间到下机...         

<think>好的,我现在需要帮助用户了解C++的学习路径或全过程。用户之前询问了C语言的学习过程,现在转向C++。根据用户提供的引用和之前的回答结构,我需要整理一个清晰的C++学习指南。 首先,用户提供的引用中有几个关键点: 1. 引用[1]提到《Effective C++》和《More Effective C++》对进阶学习很重要,强调实战经验和优化技巧。 2. 引用[2]推荐了WebServer项目,参考书籍如《Linux高性能服务器编程》和《Linux多线程服务器编程》,并提到自制网络库提升编码能力。 3. 引用[3]指出如果有C语言基础,学习C++需要三四个月,否则需要先学C语言,并推荐了C语言教程。 4. 引用[4]涉及编译过程,尤其是链接阶段,这对理解C++底层机制有帮助。 用户之前的回答结构分为阶段,每个阶段有目标、核心内容、关键要点和实战项目。我需要沿用似结构,但针对C++调整内容。同时,需要确保使用正确的LaTeX格式,如行内公式用$...$,独立公式用$$...$$,并生成相关问题。 接下来,我需要考虑C++的学习路径通常包括: 1. 基础语法和面向对象编程。 2. 标准库和模板的使用。 3. 内存管理和高级特性(如智能指针)。 4. 系统级编程和网络开发。 5. 性能优化和设计模式。 6. 实战项目,如Web服务器、游戏开发等。 结合引用内容,用户可能需要强调项目实战,比如WebServer的实现,以及参考推荐的书籍。此外,用户之前的问题中提到了C语言学习,所以可能需要对比C和C++的不同,尤其是在面向对象和内存管理方面。 需要注意引用[3]提到,有C语言基础可以缩短学习时间,因此可能需要分情况讨论学习路径。但用户现在的问题是关于C++的全过程,所以应假设用户可能已经有C基础,或者需要涵盖从零开始的情况。 需要确保回答结构清晰,分阶段,每个阶段有目标、核心内容、关键点、实战项目或书籍推荐,并引用相关引用内容。最后生成相关问题,如如何优化C++性能、多线程编程注意事项等。 另外,检查是否遵循了系统级指令:使用正确的中文,LaTeX格式正确,生成相关问题,引用标识正确。</think>#### C++全路径学习指南(含实战进阶体系) --- ### 一、语言基础构建阶段(4-6周) **目标**:掌握C++核心语法与面向对象范式 **核心内容**: 1. **开发环境配置** - 安装G++/Clang编译器(验证命令:`g++ --version`) - 配置VS Code/CLion开发环境(集成CMake与GDB调试) 2. **语法特性精要** - 面向对象三要素:封装(设计)、继承(派生)、多态(虚函数表) - 运算符重载与型转换(如`$std::string operator+(const std::string& a, const std::string& b)$`) ```cpp // 引用[3]进阶示例 class Shape { public: virtual double area() const = 0; // 纯虚函数实现多态 }; class Circle : public Shape { double radius; public: Circle(double r) : radius(r) {} double area() const override { return 3.14159 * radius * radius; } }; ``` 3. **标准库初探** - STL容器:`vector`, `map`, `unordered_set` - 智能指针:`unique_ptr`, `shared_ptr`(对比引用[2]中的原生指针使用) **关键要点**: - 理解RAII(资源获取即初始化)原则[^1] - 区分堆栈内存管理(引用[4]编译链接阶段的内存分配机制) --- ### 二、系统编程强化阶段(8-10周) **目标**:深入操作系统交互与性能优化 **核心内容**: 1. **内存管理进阶** - 自定义内存池设计(解决引用[2]中高频内存分配问题) - 移动语义与完美转发(`std::move`与`std::forward`) 2. **并发编程体系** - 线程管理:`std::thread`与`std::async` - 同步机制:`std::mutex`, `std::atomic`(如引用[2]WebServer的高并发需求) ```cpp // 多线程安全队列示例 template<typename T> class ThreadSafeQueue { std::queue<T> data; mutable std::mutex mtx; public: void push(T val) { std::lock_guard<std::mutex> lock(mtx); data.push(std::move(val)); } bool try_pop(T& val) { std::lock_guard<std::mutex> lock(mtx); if(data.empty()) return false; val = std::move(data.front()); data.pop(); return true; } }; ``` 3. **网络编程实战** - Socket编程基础(参考引用[2]中WebServer实现) - 使用Boost.Asio或muduo网络库(如引用[2]提到的自制网络库) **推荐书籍**: - 《Effective C++》55条黄金准则(引用[1]核心推荐) - 《Linux多线程服务器编程》(引用[2]进阶指南) --- ### 三、工程化实践阶段(12周+) **目标**:构建可维护的工业级系统 **核心内容**: 1. **模板元编程** - 型萃取(`std::enable_if`, `std::is_same`) - 变参模板与折叠表达式(如`$template<typename... Args> void log(Args&&... args)$`) 2. **设计模式应用** - 观察者模式(GUI事件处理) - 工厂模式(对象创建解耦) 3. **性能调优技巧** - 缓存友好代码设计(空间局部性原则) - 使用`perf`工具分析热点函数 **实战项目**: - 实现HTTP服务器(参考引用[2]的TinyWebServer) - 开发简易游戏引擎(应用ECS架构) --- ### 四、专家级能力拓展(持续) **目标**:掌握前沿技术与架构设计 **核心内容**: 1. **跨平台开发** - CMake高级用法(交叉编译配置) - 处理平台特异性代码(`#ifdef _WIN32`) 2. **分布式系统** - RPC框架开发(结合Protobuf与gRPC) - 分布式锁实现(Redis/ZooKeeper集成) 3. **编译器原理应用** - Clang AST解析(自定义代码分析工具) - LLVM IR优化(如引用[4]编译过程深度控制) --- ### 五、学习路径对照表 | 阶段 | 核心能力 | 对应引用内容 | |---------------|-----------------------------|---------------------------| | 语法基础 | 面向对象/STL | 引用[1]进阶方法论 | | 系统编程 | 并发/网络编程 | 引用[2]项目实战经验 | | 工程实践 | 设计模式/性能优化 | 引用[3]工程化学习路线 | | 编译原理 | 程序编译链接机制 | 引用[4]编译全过程解析 | --- ### 六、高频问题解决方案 1. **对象生命周期管理** - 优先使用智能指针替代原生指针(`make_shared`工厂方法) - 使用ScopeGuard实现异常安全[^1] 2. **模板编译错误排查** ```cpp // 型静态断言 template<typename T> void process(T val) { static_assert(std::is_arithmetic<T>::value, "需数值型"); //... } ``` 3. **跨平台兼容性处理** - 使用`<chrono>`替代平台相关时间函数 - 通过vcpkg/conan管理第三方依赖 --- #### 能力成长公式 $$专家水平 = \frac{\text{项目经验} \times \text{底层理解}}{\text{知识盲区}} + \epsilon$$ 成长建议: 1. 完成3个以上完整项目(如引用[2]的WebServer) 2. 精读《Effective C++》并实践所有条款 3. 参与开源社区(如贡献LLVM或Boost代码) --- #### 相关问题 1. 如何用C++11特性优化传统面向对象设计? 2. 在多线程环境中如何避免虚假共享(False Sharing)? 3. C++移动语义在实际项目中的应用场景有哪些? 4. 如何通过CMake管理大型C++项目的依赖关系? 5. 在分布式系统中如何设计高效的C++序列化方案? [^1]: 综合《Effective C++》与《More Effective C++》核心方法论 [^2]: 参照TinyWebServer实现与《Linux高性能服务器编程》 [^3]: 结合零基础C++学习路线与项目实践指导 [^4]: 解析C/C++编译链接机制与底层实现
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值