<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>note &#8211; 架构师之路</title>
	<atom:link href="https://www.greatfar.cn/archives/category/note/feed" rel="self" type="application/rss+xml" />
	<link>https://www.greatfar.cn</link>
	<description>Greatfar, 高级系统架构设计师，精通Go、PHP、C++、Java，分享技术，软考系统架构设计师学习笔记</description>
	<lastBuildDate>Thu, 01 Jun 2023 07:40:11 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.4.2</generator>
	<item>
		<title>Go 运行机制</title>
		<link>https://www.greatfar.cn/archives/35</link>
		
		<dc:creator><![CDATA[Greatfar]]></dc:creator>
		<pubDate>Fri, 19 May 2023 12:26:59 +0000</pubDate>
				<category><![CDATA[note]]></category>
		<guid isPermaLink="false">http://www.greatfar.cn/?p=35</guid>

					<description><![CDATA[Go 运行时（go runtime）的含义？ go 运行时，也称为 &#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p><strong></strong><a href="https://blog.csdn.net/qq_41000891/article/details/120463494"></a><strong>Go 运行时（go </strong><strong>runtime</strong><strong>）的含义？</strong></p>



<p>go 运行时，也称为 go runtime。其本身就<strong>是每个 go 程序的一部分，</strong>它会跟你的源码一起编译并连接到目标程序中。<strong>即便你的代码只是写了一个 hello world 功能，编译成二进制程序后，这个程序中也包含了 </strong><strong>runtime</strong><strong> 的实现。</strong></p>



<p>它在程序中具体负责什么？runtime 负责实现 Go 的垃圾收集、并发、内存堆栈管理、协程调度器以及 Go 语言的其他关键功能。</p>



<p>go源码编译成go程序后，go程序无需依赖独立安装的go运行时。（与java这种依赖JVM解析运行的编程语言不同）</p>



<p><strong>为什么Go语言适合现代的后端编程环境？</strong></p>



<ol>
<li>服务类应用以API居多，IO密集型，且网络IO最多；</li>



<li>运行成本低，<strong>无VM</strong>。网络连接数不多的情况下内存占用低；</li>



<li>强类型语言，易上手，易维护；</li>
</ol>



<p><strong>为什么适合基础设施？</strong></p>



<ul>
<li>k8s、etcd、istio、docker已经证明了Go的能力</li>
</ul>



<p><strong>&nbsp;Go语言的编译过程</strong></p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="225" src="https://www.greatfar.cn/wp-content/uploads/2023/05/image-4-1024x225.png" alt="" class="wp-image-36" srcset="https://www.greatfar.cn/wp-content/uploads/2023/05/image-4-1024x225.png 1024w, https://www.greatfar.cn/wp-content/uploads/2023/05/image-4-300x66.png 300w, https://www.greatfar.cn/wp-content/uploads/2023/05/image-4-768x169.png 768w, https://www.greatfar.cn/wp-content/uploads/2023/05/image-4-1536x338.png 1536w, https://www.greatfar.cn/wp-content/uploads/2023/05/image-4-2048x450.png 2048w, https://www.greatfar.cn/wp-content/uploads/2023/05/image-4-1568x345.png 1568w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>使用go build -x xxx.go可以观察这个过程</p>



<p><strong>不同系统的可执行文件规范</strong></p>



<p>以Linux的可执⾏⽂件ELF(Executable and Linkable Format) 为例，ELF&nbsp;由⼏部分构成：</p>



<ul>
<li>ELF header</li>



<li>Section header</li>



<li>Sections</li>
</ul>



<p>操作系统执行可执行文件的步骤（Linux为例）：</p>



<p><strong>如何寻找Go进程的入口</strong></p>



<p>通过 entry point 找到&nbsp;Go进程的执⾏⼊⼝点，使⽤readelf</p>



<p>⼊⼝点指向编译后的main函数</p>



<p></p>



<p><a href="https://blog.csdn.net/qq_41000891/article/details/120463494"></a></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>go 并发锁 Mutex</title>
		<link>https://www.greatfar.cn/archives/32</link>
					<comments>https://www.greatfar.cn/archives/32#respond</comments>
		
		<dc:creator><![CDATA[Greatfar]]></dc:creator>
		<pubDate>Fri, 19 May 2023 12:25:49 +0000</pubDate>
				<category><![CDATA[note]]></category>
		<guid isPermaLink="false">http://www.greatfar.cn/?p=32</guid>

					<description><![CDATA[Mutex的概念 使用场景 多个线程同时访问临界区，为保证数据的安全&#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p><strong>Mutex的概念</strong></p>



<ol>
<li>Go sync包提供了两种<strong>锁</strong>类型：互斥锁sync.Mutex 和 读写互斥锁sync.RWMutex，都属于<strong>悲观锁</strong>。</li>



<li>Mutex是<strong>互斥锁</strong>，当一个 goroutine 获得了锁后，其他 goroutine 不能获取锁（只能存在一个写者或读者，不能同时读和写）</li>
</ol>



<p><strong>使用场景</strong></p>



<p>多个线程同时访问临界区，为保证数据的安全，锁住一些共享资源， 以防止并发访问这些共享数据时可能导致的数据不一致问题</p>



<p><strong>底层实现结构</strong></p>



<p>互斥锁对应的是底层结构是sync.Mutex<a href="https://so.csdn.net/so/search?q=%E7%BB%93%E6%9E%84%E4%BD%93&amp;spm=1001.2101.3001.7020">结构体</a>，，位于 src/sync/mutex.go中</p>



<p>type Mutex struct { state int32 sema uint32 }</p>



<p>state表示锁的状态，有锁定、被唤醒、饥饿模式等，并且是用state的二进制位来标识的，不同模式下会有不同的处理方式</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="538" src="https://www.greatfar.cn/wp-content/uploads/2023/05/image-3-1024x538.png" alt="" class="wp-image-33" srcset="https://www.greatfar.cn/wp-content/uploads/2023/05/image-3-1024x538.png 1024w, https://www.greatfar.cn/wp-content/uploads/2023/05/image-3-300x157.png 300w, https://www.greatfar.cn/wp-content/uploads/2023/05/image-3-768x403.png 768w, https://www.greatfar.cn/wp-content/uploads/2023/05/image-3.png 1162w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>sema表示信号量，mutex阻塞队列的定位是通过这个变量来实现的，从而实现goroutine的阻塞和唤醒</p>



<figure class="wp-block-image"><img decoding="async" src="https://note.youdao.com/yws/res/57387/WEBRESOURCE7c087918d35f8ecec77a25d6698e2b74" alt="0"/></figure>



<p><strong>操作</strong></p>



<p>锁的实现一般会依赖于原子操作、信号量，通过atomic 包中的一些原子操作来实现锁的锁定，通过信号量来实现线程的阻塞与唤醒</p>



<p><strong>加锁</strong></p>



<p>通过原子操作cas加锁，如果加锁不成功，根据不同的场景选择自旋重试加锁或者阻塞等待被唤醒后加锁</p>



<figure class="wp-block-image"><img decoding="async" src="https://note.youdao.com/yws/res/57393/WEBRESOURCEe07851b734965ccf85855a10731f9d2d" alt="0"/></figure>



<p>func (m *Mutex) Lock() { // Fast path: 幸运之路，一下就获取到了锁 if atomic.CompareAndSwapInt32(&amp;m.state, 0, mutexLocked) { return } // Slow path：缓慢之路，尝试自旋或阻塞获取锁 m.lockSlow() }</p>



<p><strong>解锁</strong></p>



<p>通过原子操作add解锁，如果仍有goroutine在等待，唤醒等待的goroutine</p>



<figure class="wp-block-image"><img decoding="async" src="https://note.youdao.com/yws/res/57399/WEBRESOURCE09705b8d6ea909917b3121a0a5dc6cad" alt="0"/></figure>



<p>func (m *Mutex) Unlock() { // Fast path: 幸运之路，解锁 new := atomic.AddInt32(&amp;m.state, -mutexLocked) if new != 0 { // Slow path：如果有等待的goroutine，唤醒等待的goroutine m.unlockSlow() } }</p>



<p><strong>注意</strong></p>



<p>在 Lock() 之前使用 Unlock() 会导致 panic 异常</p>



<p>使用 Lock() 加锁后，再次 Lock() 会导致死锁（不支持重入），需Unlock()解锁后才能再加锁</p>



<p>锁定状态与 goroutine 没有关联，一个 goroutine 可以 Lock，另一个 goroutine 可以 Unlock</p>



<p><strong>互斥锁允许自旋的条件</strong></p>



<p><a href="undefined"></a><a href="undefined"></a><strong>线程没有获取到锁时常见有2种处理方式</strong></p>



<ol>
<li>一种是没有获取到锁的线程就一直循环等待判断该资源是否已经释放锁，这种锁也叫做自旋锁，它不用将线程阻塞起来， 适用于并发低且程序执行时间短的场景，缺点是cpu占用较高</li>



<li>另外一种处理方式就是把自己阻塞起来，会释放CPU给其他线程，内核会将线程置为「睡眠」状态，等到锁被释放后，内核会在合适的时机唤醒该线程，适用于高并发场景，缺点是有线程上下文切换的开销</li>
</ol>



<p>Go语言中的Mutex实现了自旋与阻塞两种场景，当满足不了自旋条件时，就会进入阻塞</p>



<p><strong>允许自旋的条件</strong></p>



<ol>
<li>锁已被占用，并且锁不处于饥饿模式。</li>



<li>积累的自旋次数小于最大自旋次数（active_spin=4）。</li>



<li>cpu 核数大于 1。</li>



<li>有空闲的 P。</li>



<li>当前 goroutine 所挂载的 P 下，本地待运行队列为空</li>
</ol>



<p><strong>可重入锁</strong></p>



<p><a href="undefined"></a><a href="undefined"></a><strong>概念</strong></p>



<p>可重入锁又称为递归锁，是以线程为单位，当一个线程获取对象锁之后，这个线程可以再次获取本对象上的锁，而其他的线程是不可以。(通俗来说：可重入锁也是一种技术，任意线程在获取锁之后，能够再次获取锁，而不会被锁所排斥)可重入锁的意义之一在于防止死锁</p>



<p><strong>Mutex不是可重入锁</strong></p>



<p>Mutex 不是可重入的锁。Mutex 的实现中没有记录哪个 goroutine 拥有这把锁。理论上，任何 goroutine 都可以随意地 Unlock 这把锁，所以没办法计算重入条件，并且Mutex 重复Lock会导致死锁</p>



<p>下面这个例子就会报错：</p>



<p>package main import ( &#8220;fmt&#8221; &#8220;sync&#8221; ) var mu sync.Mutex var chain string func A(){ mu.Lock() defer mu.Unlock() chain = chain + &#8220;&#8211;&gt;A&#8221; B() } func B(){ chain = chain + &#8220;&#8211;&gt;B&#8221; C() } func C(){ mu.Lock() defer mu.Unlock() chain = chain + &#8220;&#8211;&gt;C&#8221; } func main() { chain = &#8220;main&#8221; A() fmt.Println(chain) }</p>



<p><strong>实现可重入锁</strong></p>



<ol>
<li>记住持有锁的线程</li>



<li>统计重入的次数</li>
</ol>



<p><strong>正常模式(非公平锁)</strong></p>



<p>在刚开始的时候，是处于正常模式（Barging），也就是，当一个G1持有着一个锁的时候，G2会自旋的去尝试获取这个锁</p>



<p>当自旋超过4次还没有能获取到锁的时候，这个G2就会被加入到获取锁的等待队列里面，并阻塞等待唤醒</p>



<p>正常模式下，所有等待锁的 goroutine 按照 FIFO(先进先出)顺序等待。唤醒的goroutine 不会直接拥有锁，而是会和新请求锁的 goroutine 竞争锁。新请求锁的 goroutine 具有优势：它正在 CPU 上执行，而且可能有好几个，所以刚刚唤醒的 goroutine 有很大可能在锁竞争中失败，长时间获取不到锁，就会切换到饥饿模式</p>



<p><strong>饥饿模式(公平锁)</strong></p>



<p>当一个 goroutine 等待锁时间超过 1 毫秒时，它可能会遇到饥饿问题。 在版本1.9中，这种场景下Go Mutex 切换到饥饿模式（handoff），解决饥饿问题</p>



<p>如果一个G进入了饥饿模式，那也不可能说永远的保持一个饥饿的状态，总归会有吃饱的时候，也就是总有那么一刻Mutex会回归到正常模式，那么回归正常模式必须具备的条件有以下几种：</p>



<p>G的执行时间小于1ms</p>



<p>等待队列已经全部清空了</p>



<p>当满足上述两个条件的任意一个的时候，Mutex会切换回正常模式，而Go的抢锁的过程，就是在这个正常模式和饥饿模式中来回切换进行的</p>



<p><strong>读写互斥锁RWMutex</strong></p>



<p><a href="undefined"></a><a href="undefined"></a><strong>概念</strong></p>



<p>读写互斥锁RWMutex，是对Mutex的一个扩展，当一个 goroutine 获得了读锁后，其他 goroutine可以获取读锁，但不能获取写锁；当一个 goroutine 获得了写锁后，其他 goroutine既不能获取读锁也不能获取写锁（只能存在一个写者或多个读者，可以同时读）</p>



<p><strong>使用场景</strong></p>



<p>读多于写的情况（既保证线程安全，又保证性能不太差）</p>



<p><strong>注意点</strong></p>



<p>读锁或写锁在 Lock() 之前使用 Unlock() 会导致 panic 异常</p>



<p>使用 Lock() 加锁后，再次 Lock() 会导致死锁（不支持重入），需Unlock()解锁后才能再加锁</p>



<p>锁定状态与 goroutine 没有关联，一个 goroutine 可以 RLock（Lock），另一个 goroutine 可以 RUnlock（Unlock）</p>



<p><strong>互斥锁和读写锁的区别</strong></p>



<ol>
<li>读写锁区分读者和写者，而互斥锁不区分</li>



<li>互斥锁同一时间只允许一个线程访问该对象，无论读写；读写锁同一时间内只允许一个写者，但是允许多个读者同时读对象</li>
</ol>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.greatfar.cn/archives/32/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Go 协程 goroutine</title>
		<link>https://www.greatfar.cn/archives/30</link>
					<comments>https://www.greatfar.cn/archives/30#respond</comments>
		
		<dc:creator><![CDATA[Greatfar]]></dc:creator>
		<pubDate>Fri, 19 May 2023 12:24:12 +0000</pubDate>
				<category><![CDATA[note]]></category>
		<guid isPermaLink="false">http://www.greatfar.cn/?p=30</guid>

					<description><![CDATA[在编写 Socket 网络程序时，需要提前准备一个线程池为每一个 S&#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p>在编写 Socket 网络程序时，需要提前准备一个线程池为每一个 Socket 的收发包分配一个线程。开发人员需要在线程数量和 CPU 数量间建立一个对应关系，以保证每个任务能及时地被分配到 CPU 上进行处理，同时避免多个任务频繁地在线程间切换执行而损失效率。</p>



<p>虽然，线程池为逻辑编写者提供了线程分配的抽象机制。但是，如果面对随时随地可能发生的并发和线程处理需求，线程池就不是非常直观和方便了。能否有一种机制：使用者分配足够多的任务，系统能自动帮助使用者把任务分配到 CPU 上，让这些任务尽量并发运作。这种机制在 Go语言中被称为&nbsp;<strong>goroutine</strong>。</p>



<p>goroutine 是 Go语言中的轻量级线程实现，由 Go 运行时（runtime）管理。Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU。</p>



<p>Go 程序从 main 包的 main() 函数开始，在程序启动时，Go 程序就会为 main() 函数创建一个默认的 goroutine。</p>



<p><strong>使用普通函数创建 goroutine</strong></p>



<p>Go 程序中使用&nbsp;<strong>go</strong>&nbsp;关键字为一个函数创建一个 goroutine。一个函数可以被创建多个 goroutine，一个 goroutine 必定对应一个函数。</p>



<p><strong>1) 格式</strong></p>



<p>为一个普通函数创建 goroutine 的写法如下：</p>



<p>go 函数名( 参数列表 )</p>



<ul>
<li>函数名：要调用的函数名。</li>



<li>参数列表：调用函数需要传入的参数。</li>
</ul>



<p>使用 go 关键字创建 goroutine 时，被调用函数的返回值会被忽略。</p>



<p>如果需要在 goroutine 中返回数据，请使用后面介绍的通道（channel）特性，通过通道把数据从 goroutine 中作为返回值传出。</p>



<p><strong>2) 例子</strong></p>



<p>使用 go 关键字，将 running() 函数并发执行，每隔一秒打印一次计数器，而 main 的 goroutine 则等待用户输入，两个行为可以同时进行。请参考下面代码：</p>



<p>package main import ( &#8220;fmt&#8221; &#8220;time&#8221; ) func running() { var times int // 构建一个无限循环 for { times++ fmt.Println(&#8220;tick&#8221;, times) // 延时1秒 time.Sleep(time.Second) } } func main() { // 并发执行程序 go running() // 接受命令行输入, 不做任何事情 var input string fmt.Scanln(&amp;input) }</p>



<p>命令行输出如下：</p>



<p>tick 1</p>



<p>tick 2</p>



<p>tick 3</p>



<p>tick 4</p>



<p>tick 5</p>



<p>代码执行后，命令行会不断地输出 tick，同时可以使用 fmt.Scanln() 接受用户输入。两个环节可以同时进行。</p>



<p>代码说明如下：</p>



<p>第 12 行，使用 for 形成一个无限循环。</p>



<p>第 13 行，times 变量在循环中不断自增。</p>



<p>第 14 行，输出 times 变量的值。</p>



<p>第 17 行，使用 time.Sleep 暂停 1 秒后继续循环。</p>



<p>第 25 行，使用 go 关键字让 running() 函数并发运行。</p>



<p>第 29 行，接受用户输入，直到按 Enter 键时将输入的内容写入 input 变量中并返回，整个程序终止。</p>



<p>这段代码的执行顺序如下图所示。</p>



<figure class="wp-block-image"><img decoding="async" src="https://note.youdao.com/yws/res/58119/WEBRESOURCE836ef914bc0435b202d724d8b9d07bef" alt="0"/></figure>



<p>这个例子中，Go 程序在启动时，运行时（runtime）会默认为 main() 函数创建一个 goroutine。在 main() 函数的 goroutine 中执行到 go running 语句时，归属于 running() 函数的 goroutine 被创建，running() 函数开始在自己的 goroutine 中执行。此时，main() 继续执行，两个 goroutine 通过 Go 程序的调度机制同时运作。</p>



<p><strong>使用匿名函数创建goroutine</strong></p>



<p>go 关键字后也可以为匿名函数或闭包启动 goroutine。</p>



<p><strong>1) 使用匿名函数创建goroutine的格式</strong></p>



<p>使用匿名函数或闭包创建 goroutine 时，除了将函数定义部分写在 go 的后面之外，还需要加上匿名函数的调用参数，格式如下：</p>



<p>go func( 参数列表 ){ 函数体 }( 调用参数列表 )</p>



<p>其中：</p>



<ul>
<li>参数列表：函数体内的参数变量列表。</li>



<li>函数体：匿名函数的代码。</li>



<li>调用参数列表：启动 goroutine 时，需要向匿名函数传递的调用参数。</li>
</ul>



<p><strong>2) 使用匿名函数创建goroutine的例子</strong></p>



<p>在 main() 函数中创建一个匿名函数并为匿名函数启动 goroutine。匿名函数没有参数。代码将并行执行定时打印计数的效果。参见下面的代码：</p>



<p>package main import ( &#8220;fmt&#8221; &#8220;time&#8221; ) func main() { go func() { var times int for { times++ fmt.Println(&#8220;tick&#8221;, times) time.Sleep(time.Second) } }() var input string fmt.Scanln(&amp;input) }</p>



<p>代码说明如下：</p>



<ul>
<li>第 10 行，go 后面接匿名函数启动 goroutine。</li>



<li>第 12～19 行的逻辑与前面程序的 running() 函数一致。</li>



<li>第 21 行的括号的功能是调用匿名函数的参数列表。由于第 10 行的匿名函数没有参数，因此第 21 行的参数列表也是空的。</li>
</ul>



<p><strong>提示</strong></p>



<p>所有 goroutine 在 main() 函数结束时会一同结束。</p>



<p>goroutine 虽然类似于线程概念，但是从调度性能上没有线程细致，而细致程度取决于 Go 程序的<strong> goroutine 调度器</strong>的实现和运行环境。</p>



<p>终止 goroutine 的最好方法就是自然返回 goroutine 对应的函数。虽然可以用 golang.org/x/net/context 包进行 goroutine 生命期深度控制，但这种方法仍然处于内部试验阶段，并不是官方推荐的特性。</p>



<p>截止 Go 1.9 版本，暂时没有标准接口获取 goroutine 的 ID。</p>



<p><a href="http://c.biancheng.net/view/93.html">http://c.biancheng.net/view/93.html</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.greatfar.cn/archives/30/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Qt和MFC的对比</title>
		<link>https://www.greatfar.cn/archives/27</link>
					<comments>https://www.greatfar.cn/archives/27#respond</comments>
		
		<dc:creator><![CDATA[Greatfar]]></dc:creator>
		<pubDate>Fri, 19 May 2023 12:21:47 +0000</pubDate>
				<category><![CDATA[note]]></category>
		<guid isPermaLink="false">http://www.greatfar.cn/?p=27</guid>

					<description><![CDATA[Qt和MFC的对比 之前用MFC做过游戏外挂，勾住游戏窗口，调用系统&#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p>Qt和MFC的对比</p>



<p>之前用MFC做过游戏外挂，勾住游戏窗口，调用系统接口，直接读取内存的数据，很容易实现。 现在用Qt写桌面应用程序，不用MFC写。</p>



<p>对比二者后，得出以下结论，或许对新学者有用： 1、MFC是封装了WIN API的，里面很多数据类型、头文件等，都是直接沿用WIN API的，所以会经常看到DWORD、WORD、HWND等这样的数据类型。需要有比较好的WIN API基础，看起来才不那么费劲。MFC可以静态，也可以动态编译，对于做外挂这些系统依赖比较大的比较好做。 2、Qt是比较抽象的封装，日常使用开发桌面程序时，基本上不会用到底层的WIN API，都是用Qt自己的类，也不会用到WORD、DWORD这些，不需要太了解WIN API。 3、会MFC的用Qt感觉很轻松，MFC比Qt学习成本和时间要付出更多。 4、商业开发的话，还是建议用Qt。因为这货可以跑在Linux上。 比如：你帮某个大厂做了一个在大屏幕上显示生产进度内容的程序，你总不能让别人装个盗版的windows去跑吧？或者还要别人另外花钱买个正版的Windows来跑你的程序？ 还是弄个免费的Linux桌面去跑比较合理，免得被微软带大厂和你一起飞。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.greatfar.cn/archives/27/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>C++ JSON解析库：jsoncpp、rapidjson</title>
		<link>https://www.greatfar.cn/archives/24</link>
					<comments>https://www.greatfar.cn/archives/24#respond</comments>
		
		<dc:creator><![CDATA[Greatfar]]></dc:creator>
		<pubDate>Fri, 19 May 2023 12:20:32 +0000</pubDate>
				<category><![CDATA[note]]></category>
		<guid isPermaLink="false">http://www.greatfar.cn/?p=24</guid>

					<description><![CDATA[{&#8220;errNum&#8221;:0,&#8221;ret&#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p>{&#8220;errNum&#8221;:0,&#8221;retMsg&#8221;:&#8221;success&#8221;,&#8221;retData&#8221;:{&#8220;address&#8221;:&#8221;\u56db\u5ddd\u7701\u5185\u6c5f\u5e02\u5a01\u8fdc\u53bf&#8221;,&#8221;sex&#8221;:&#8221;M&#8221;,&#8221;birthday&#8221;:&#8221;1990-12-26&#8243;}}</p>



<p>我们需要从中提取出性别，地址和出生年月等信息。这就需要json解析，我用的是开源jsoncpp库。</p>



<ol>
<li>下载地址：<a href="http://sourceforge.net/projects/jsoncpp/">http://sourceforge.net/projects/jsoncpp/</a> 或者<a href="https://github.com/open-source-parsers/jsoncpp">https://github.com/open-source-parsers/jsoncpp</a> ；下载后解压。</li>



<li>有两种使用方法，一种是<strong>自己编译成lib静态库</strong>，另外一种是<strong>直接使用源代码</strong>。</li>
</ol>



<p>自己编译是在/makefiles/vs71目录，VS打开编译即可。</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="593" src="https://www.greatfar.cn/wp-content/uploads/2023/05/image-2-1024x593.png" alt="" class="wp-image-25" srcset="https://www.greatfar.cn/wp-content/uploads/2023/05/image-2-1024x593.png 1024w, https://www.greatfar.cn/wp-content/uploads/2023/05/image-2-300x174.png 300w, https://www.greatfar.cn/wp-content/uploads/2023/05/image-2-768x445.png 768w, https://www.greatfar.cn/wp-content/uploads/2023/05/image-2.png 1094w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>但是我编译后使用发现错误，LNK2038 RuntimeLibrary 不匹配什么的，还有库与微软默认库冲突，后来还是用的源代码。</p>



<p>注意事项:</p>



<p>1.如果自己编译dll，那个版本的VS编译的库就需要哪个版本的VS使用，其他版本使用会出错；</p>



<p>2.C/C++-代码生成-运行库 这里选项需要注意，编译时什么选择，使用也应该是一样的选择；</p>



<p>3. 直接源码有两个目录需要拷贝，一个是/src/lib_json，直接拷贝文件到工程下即可，另外一个是/include/json下文件，此时注意要文件夹json一起拷贝。</p>



<p>/#include json/value.h</p>



<p>可能需要改为：</p>



<p>#include &#8220;json/value.h&#8221;</p>



<p>工程目录结构：</p>



<figure class="wp-block-image"><img decoding="async" src="https://note.youdao.com/yws/res/49335/WEBRESOURCE219fa7ccbf8321af8d9813fbb60849f1" alt="0"/></figure>



<p>jsoncpp的include路径为：</p>



<figure class="wp-block-image"><img decoding="async" src="https://note.youdao.com/yws/res/49336/WEBRESOURCE271f2ebca755d6ddf940376d65a041c6" alt="0"/></figure>



<p>jsoncpp官方文档：</p>



<p><a href="http://open-source-parsers.github.io/jsoncpp-docs/doxygen/index.html">http://open-source-parsers.github.io/jsoncpp-docs/doxygen/index.html</a></p>



<p>MFC调用示例：（最好自己参看官方文档，示例不一定适用）</p>



<p>#include &lt;afxinet.h&gt; #include &#8220;json.h&#8221; void CTestJsonDlg::OnBnClickedButton1() { //使用百度api获取数据&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211; char buf[1000] = { 0 }; CString StrDate, strValue; CInternetSession session; CHttpFile* file = NULL; int nRead; file = (CHttpFile *)session.OpenURL(_T(&#8220;http://apistore.baidu.com/microservice/icardinfo?id=xxxxxxxxxxxxxx&#8221;)); if (NULL != file){ //Do something here with the web request //Clean up the file here to avoid a memory leak!!!!!!! while ((nRead = file-&gt;Read(buf, sizeof(buf))) &gt; 0){ StrDate = buf; } GetDlgItem(IDC_ID_CARD)-&gt;SetWindowText(StrDate); file-&gt;Close(); delete file; } session.Close(); //解析json数据&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211; CString strTemp; std::string value; Json::Reader reader; Json::Value root; if (reader.parse(buf, root)) // reader将Json字符串解析到root，root将包含Json里所有子元素 { int errNum = root[&#8220;errNum&#8221;].asInt();// 访问节点，errNum = 0 std::string retMsg = root[&#8220;retMsg&#8221;].asString(); // 访问节点，retMsg = &#8220;success&#8221; strTemp = retMsg.c_str(); MessageBox(strTemp); Json::Value list = root[&#8220;retData&#8221;]; //包含addresss,sex,birthday if (!list.isNull()){ // int count = list.size(); Json::Value::Members members(list.getMemberNames()); // std::sort(members.begin(), members.end()); //解析后面的嵌套json搞了半天，最后才在测试代码中发现解决方案。 for (auto it = members.begin(); it != members.end();++it) { const std::string &amp;name = *it; std::string str = list[name].asCString(); // strTemp = str.c_str(); strTemp = UTF8ToUnicode(const_cast&lt;char *&gt;(str.c_str())); MessageBox(strTemp); } } } }</p>



<p>5 注意事项：</p>



<p>一个需要注意的地方是编码问题，由于地址是中文，所以需要进行UTF8ToUnicode转码。</p>



<p>还有添加的头文件需要去掉添加预处理头选项，或者#include “StdAfx.h”。</p>



<p>其他Error:</p>



<ol>
<li>json_value.asm”: No such file or directory</li>



<li>jsoncpp fatal error link1257</li>
</ol>



<p>编译库文件时，配置属性-常规-全程序优化 无全程序优化。</p>



<p>C/C++ -输出文件 &#8211; 汇编程序输出：无列表。</p>



<p>即可。</p>



<p><a href="https://blog.csdn.net/a379039233/article/details/49424393">https://blog.csdn.net/a379039233/article/details/49424393</a></p>



<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-</p>



<p><a href="https://www.jianshu.com/p/017c13a65622">https://www.jianshu.com/p/017c13a65622</a></p>



<p>以前VC解析JSON用的是第三方库JSONCpp，但是JSONCpp对我现在要做的项目来说有缺陷，不支持Unicode的CString解析，要经过繁琐的转化，网上也缺少用的信息来解决这个问题。现在用RapidJson试着来解决这个问题。</p>



<p>简介</p>



<p><strong>RapidJson</strong>是<strong>腾讯</strong>推出的轻量级的高效的 C++ JSON 解析／生成器。说他高效肯定有些人不服，那么有多高效呢？这里有他的作者对 <a href="https://github.com/miloyip/nativejson-benchmark">28 个 C/C++ JSON 库的比较</a>，即便是老牌的JsonCpp库在效率上依然跟他差了一个量级。更让我看重的是他下面这个优点</p>



<p><a href="http://rapidjson.org/zh-cn/">http://rapidjson.org/zh-cn/</a></p>



<figure class="wp-block-image"><img decoding="async" src="https://note.youdao.com/yws/res/49337/WEBRESOURCE06a512d756acf5a9241f4ee24f374cf0" alt="0"/></figure>



<p><a href="https://github.com/Tencent/rapidjson">https://github.com/Tencent/rapidjson</a></p>



<p>RapidJSON 是一个 C++ 的 JSON 解析器及生成器。它的灵感来自&nbsp;<a href="http://rapidxml.sourceforge.net/">RapidXml</a>。</p>



<ul>
<li>RapidJSON 小而全。它同时支持 SAX 和 DOM 风格的 API。SAX 解析器只有约 500 行代码。</li>



<li>RapidJSON 快。它的性能可与&nbsp;</li>
</ul>



<p>strlen()&nbsp;相比。可支持 SSE2/SSE4.2 加速。</p>



<ul>
<li>RapidJSON 独立。它不依赖于 BOOST 等外部库。它甚至不依赖于 STL。</li>



<li>RapidJSON 对内存友好。在大部分 32/64 位机器上，每个 JSON 值只占 16 字节（除字符串外）。它预设使用一个快速的内存分配器，令分析器可以紧凑地分配内存。</li>



<li>RapidJSON 对 Unicode 友好。它支持 UTF-8、UTF-16、UTF-32 (大端序／小端序)，并内部支持这些编码的检测、校验及转码。例如，RapidJSON 可以在分析一个 UTF-8 文件至 DOM 时，把当中的 JSON 字符串转码至 UTF-16。它也支持代理对（surrogate pair）及&nbsp;&#8220;\u0000&#8243;（空字符）。</li>
</ul>



<p>在&nbsp;<a href="http://rapidjson.org/zh-cn/doc/features.zh-cn.md">这里</a>&nbsp;可读取更多特点。</p>



<p>JSON（JavaScript Object Notation）是一个轻量的数据交换格式。RapidJSON 应该完全遵从 RFC7159/ECMA-404，并支持可选的放宽语法。 关于 JSON 的更多信息可参考：</p>



<p><strong>安装</strong></p>



<p>RapidJSON 是只有头文件的 C++ 库。只需把&nbsp;include/rapidjson&nbsp;目录复制至系统或项目的 include 目录中<strong>（VS2022需要在项目属性自行添加include目录）</strong>。</p>



<p>使用示例：</p>



<p><a href="https://github.com/Tencent/rapidjson/#usage-at-a-glance">https://github.com/Tencent/rapidjson/#usage-at-a-glance</a></p>



<p><a href="https://github.com/Tencent/rapidjson/#usage-at-a-glance">https://github.com/Tencent/rapidjson/#usage-at-a-glance</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.greatfar.cn/archives/24/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>字符集：多字节编码 与 Unicode码</title>
		<link>https://www.greatfar.cn/archives/22</link>
					<comments>https://www.greatfar.cn/archives/22#respond</comments>
		
		<dc:creator><![CDATA[Greatfar]]></dc:creator>
		<pubDate>Fri, 19 May 2023 12:19:35 +0000</pubDate>
				<category><![CDATA[note]]></category>
		<guid isPermaLink="false">http://www.greatfar.cn/?p=22</guid>

					<description><![CDATA[转： 带你玩转Visual Studio——带你理解多字节编码与Un&#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p><strong></strong><strong></strong><a href="undefined">转： 带你玩转Visual Studio——带你理解多字节编码与Unicode码</a><strong></strong></p>



<p><strong>多字节字符与宽字节字符</strong></p>



<p><a href="https://www.lmonkey.com/t/DExgl2ZBK"></a><strong>char 与 wchar_t</strong></p>



<p>我们知道C++基本数据类型中表示字符的有两种：char、wchar_t。&nbsp;</p>



<p>char叫<strong>多字节字符</strong>，一个char占一个字节，<strong>之所以</strong>叫多字节字符是因为它表示一个<strong>字</strong>时可能是一个字节也可能是多个字节。一个英文字符(如’s’)用一个char(一个字节)表示，一个中文汉字(如’中’)用3个char(三个字节)表示，看下面的例子。</p>



<p>void TestChar() { char ch1 = &#8216;s&#8217;; // 正确 cout &lt;&lt; &#8220;ch1:&#8221; &lt;&lt; ch1 &lt;&lt; endl; char ch2 = &#8216;中&#8217;; // 错误,一个char不能完整存放一个汉字信息 cout &lt;&lt; &#8220;ch2:&#8221; &lt;&lt; ch2 &lt;&lt; endl; char str[4] = &#8220;中&#8221;; //前三个字节存放汉字&#8217;中&#8217;,最后一个字节存放字符串结束符\0 cout &lt;&lt; &#8220;str:&#8221; &lt;&lt; str &lt;&lt; endl; //char str2[2] = &#8220;国&#8221;; // 错误：&#8217;str2&#8242; : array bounds overflow //cout &lt;&lt; str2 &lt;&lt; endl; }</p>



<p>结点如下：</p>



<p>ch1:s&nbsp;ch2:&nbsp;str:中</p>



<p>wchar_t被称为<strong>宽字符</strong>，<strong>一个wchar_t占2个字节。之所以叫宽字符是因为所有的字都要用两个字节(即一个wchar_t)来表示，不管是英文还是中文</strong>。看下面的例子：</p>



<p>void TestWchar_t() { wcout.imbue(locale(&#8220;chs&#8221;)); // 将wcout的本地化语言设置为中文 wchar_t wch1 = L&#8217;s&#8217;; // 正确 wcout &lt;&lt; &#8220;wch1:&#8221; &lt;&lt; wch1 &lt;&lt; endl; wchar_t wch2 = L&#8217;中&#8217;; // 正确,一个汉字用一个wchar_t表示 wcout &lt;&lt; &#8220;wch2:&#8221; &lt;&lt; wch2 &lt;&lt; endl; wchar_t wstr[2] = L&#8221;中&#8221;; // 前两个字节(前一个wchar_t)存放汉字&#8217;中&#8217;,最后两个字节(后一个wchar_t)存放字符串结束符\0 wcout &lt;&lt; &#8220;wstr:&#8221; &lt;&lt; wstr &lt;&lt; endl; wchar_t wstr2[3] = L&#8221;中国&#8221;; wcout &lt;&lt; &#8220;wstr2:&#8221; &lt;&lt; wstr2 &lt;&lt; endl; }</p>



<p>结果如下：</p>



<p>ch1:s&nbsp;ch2:中&nbsp;str:中&nbsp;str2:中国</p>



<p><strong>说明：</strong></p>



<p>1. 用常量字符给wchar_t变量赋值时，前面要加L 如： wchar_t wch2 = L’中’;&nbsp;</p>



<p>2. 用常量字符串给wchar_t数组赋值时,前面要加L。如： wchar_t wstr2[3] = L”中国”;&nbsp;</p>



<p><strong>3. 如果不加L，对于英文可以正常，但对于非英文(如中文)会出错。</strong></p>



<p><strong>string与wstring</strong></p>



<p>字符数组可以表示一个字符串，但它是一个定长的字符串，我们在使用之前必须知道这个数组的长度。为方便字符串的操作，STL为我们定义好了字符串的类string和wstring。大家对string肯定不陌生，但wstring可能就用的少了。</p>



<p>string是普通的多字节版本，是基于char的，对char数组进行的一种封装。</p>



<p>wstring是Unicode版本，是基于wchar_t的，对wchar_t数组进行的一种封装。</p>



<p><a href="https://www.lmonkey.com/t/DExgl2ZBK"></a><strong>string 与 wstring的相关转换：</strong></p>



<p>以下的两个方法是跨平台的，可在Windows下使用，也可在Linux下使用。</p>



<p>#include &lt;cstdlib&gt; #include &lt;string.h&gt; #include &lt;string&gt; // wstring =&gt; string std::string WString2String(const std::wstring&amp; ws) { std::string strLocale = setlocale(LC_ALL, &#8220;&#8221;); const wchar_t* wchSrc = ws.c_str(); size_t nDestSize = wcstombs(NULL, wchSrc, 0) + 1; char *chDest = new char[nDestSize]; memset(chDest,0,nDestSize); wcstombs(chDest,wchSrc,nDestSize); std::string strResult = chDest; delete []chDest; setlocale(LC_ALL, strLocale.c_str()); return strResult; } // string =&gt; wstring std::wstring String2WString(const std::string&amp; s) { std::string strLocale = setlocale(LC_ALL, &#8220;&#8221;); const char* chSrc = s.c_str(); size_t nDestSize = mbstowcs(NULL, chSrc, 0) + 1; wchar_t* wchDest = new wchar_t[nDestSize]; wmemset(wchDest, 0, nDestSize); mbstowcs(wchDest,chSrc,nDestSize); std::wstring wstrResult = wchDest; delete []wchDest; setlocale(LC_ALL, strLocale.c_str()); return wstrResult; }</p>



<p><strong>字符集(Charcater Set)与字符编码(Encoding)</strong></p>



<p><a href="https://www.lmonkey.com/t/DExgl2ZBK"></a><strong>字符集（Charcater Set或Charset）：</strong>是一个系统支持的所有抽象字符的集合，也就是一系列字符的集合。字符是各种文字和符号的总称，包括各国家文字、标点符号、图形符号、数字等。常见的字符集有：ASCII字符集、GB2312字符集(主要用于处理中文汉字)、GBK字符集(主要用于处理中文汉字)、Unicode字符集等。</p>



<p><strong>字符编码（Character Encoding）：</strong>是一套法则，使用该法则能够对自然语言的字符的一个字符集（如字母表或音节表），与计算机能识别的二进制数字进行配对。即它能在符号集合与数字系统之间建立对应关系，是信息处理的一项基本技术。通常人们用符号集合（一般情况下就是文字）来表达信息，而计算机的信息处理系统则是以二进制的数字来存储和处理信息的。字符编码就是将符号转换为计算机能识别的二进制编码。</p>



<p>一般一个字符集等同于一个编码方式，ANSI体系(ANSI是一种字符代码，为使计算机支持更多语言，通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符)的字符集如ASCII、ISO 8859-1、BIG5、GB2312、JIS、GBK等等。一般我们说一种编码都是针对某一特定的字符集。&nbsp;</p>



<p>一个字符集上也可以有多种编码方式，例如UCS字符集(也是Unicode使用的字符集)上有UTF-8、UTF-16、UTF-32等编码方式。</p>



<p>从计算机字符编码的发展历史角度来看，大概经历了三个阶段：&nbsp;</p>



<p><strong>第一个阶段</strong>：ASCII字符集 和 ASCII编码</p>



<p>计算机刚开始只支持英语(即拉丁字符)，其它语言不能够在计算机上存储和显示。ASCII用一个字节(Byte)的7位(bit)表示一个字符，第一位置0。后来为了表示更多的欧洲常用字符又对ASCII进行了扩展，又有了EASCII，EASCII用8位表示一个字符，使它能多表示128个字符，支持了部分西欧字符。</p>



<p><strong>第二个阶段</strong>: <strong>ANSI</strong>编码（本地化）&nbsp;</p>



<p>为使计算机支持更多语言，通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。比如：汉字 ‘中’ 在中文操作系统中，使用 [0xD6,0xD0] 这两个字节存储。&nbsp;</p>



<p>不同的国家和地区制定了不同的标准，由此产生了 GB2312、GBK、BIG5、JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式，称为 ANSI 编码。在简体中文系统下，ANSI 编码代表 GB2312 编码，在日文操作系统下，ANSI 编码代表 JIS 编码。&nbsp;</p>



<p><strong>不同 ANSI 编码之间互不兼容</strong>，当信息在国际间交流时，无法将属于两种语言的文字，存储在同一段 ANSI 编码的文本中。</p>



<p><strong>第三个阶段</strong>：UNICODE（国际化）&nbsp;</p>



<p>为了使国际间信息交流更加方便，国际组织制定了 UNICODE 字符集，为各种语言中的每一个字符设定了统一并且唯一的数字编号，以满足跨语言、跨平台进行文本转换、处理的要求。UNICODE 常见的有三种编码方式: UTF-8 (1个字节表示)、UTF-16 (2个字节表示)、UTF-32 (4个字节表示)。</p>



<p>我们可以用一个树状图来表示由ASCII发展而来的各个字符集和编码的分支：&nbsp;&nbsp;</p>



<figure class="wp-block-image"><img decoding="async" src="https://note.youdao.com/yws/res/55627/WEBRESOURCEf273570885bf7f49af8e1c46807d89a0" alt="0"/></figure>



<p>如果要更详细地了解字符集和字符编码请参考：&nbsp;</p>



<p><a href="https://www.lmonkey.com/t/DExgl2ZBK">字符集和字符编码（Charset &amp; Encoding）</a></p>



<p>右键你的工程名-&gt;Properties，设置如下：&nbsp;&nbsp;</p>



<p>图 2：visual studio 的 Character Set&nbsp;字符集设置</p>



<figure class="wp-block-image"><img decoding="async" src="https://note.youdao.com/yws/res/55628/WEBRESOURCE1dba53fb107d9fda2112d7587d14fddd" alt="0"/></figure>



<ol>
<li>当设置为Use Unicode Character Set时，会有预编译宏：_UNICODE、UNICODE</li>



<li>当设置为Use Multi-Byte Character Set时，会有预编译宏：_MBCS&nbsp;</li>
</ol>



<p><a href="https://www.lmonkey.com/t/DExgl2ZBK"></a></p>



<p><strong>Unicode Character Set 和 Multi-Byte Character Set这两个设置有什么区别呢？我们来看一个例子:&nbsp;</strong></p>



<p>有一个程序需要用MessageBox弹出提示框：</p>



<p>#include &#8220;windows.h&#8221; void TestMessageBox() { ::MessageBox(NULL, &#8220;这是一个测试程序!&#8221;, &#8220;Title&#8221;, MB_OK); }</p>



<p>上面这个Demo非常简单不用多说了吧！我们将Character Set设置为Multi-Byte Character Set时，可以正常编译和运行。但当我们设置为Unicode Character Set，则会有以下编译错误：</p>



<p>error C2664: ‘MessageBoxW’ : cannot convert parameter 2 from ‘const char [18]’ to ‘LPCWSTR’</p>



<p>这是因为MessageBox有两个版本，一个MessageBoxW针对Unicode版的，一个是MessageBoxA针对Multi-Byte的，它们通过不同宏进行隔开，预设不同的宏会使用不同的版本。我们使用了Use Unicode Character Set就预设了_UNICODE、UNICODE宏，所以编译时就会使用MessageBoxW，这时我们传入多字节常量字符串肯定会有问题，而应该传入宽符的字符串，即将”Title”改为L”Title”就可以了，”这是一个测试程序!”也一样。</p>



<p>WINUSERAPI int WINAPI MessageBoxA( __in_opt HWND hWnd, __in_opt LPCSTR lpText, __in_opt LPCSTR lpCaption, __in UINT uType); WINUSERAPI int WINAPI MessageBoxW( __in_opt HWND hWnd, __in_opt LPCWSTR lpText, __in_opt LPCWSTR lpCaption, __in UINT uType); #ifdef UNICODE #define MessageBox MessageBoxW #else #define MessageBox MessageBoxA #endif // !UNICODE</p>



<p><strong>上面的 </strong><strong>Multi-Byte Character Set </strong><strong>一般是指 </strong><strong>ANSI（多字节）</strong><strong>字符集</strong><strong>，</strong>关于ANSI请参考第二小节字符集(Charcater Set)与字符编码(Encoding) ——（ANSI是一种字符代码，早期为使计算机支持更多语言，通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符的字符集，各个国家制定了各自的字符编码规则，如ASCII、ISO 8859-1、BIG5、GB2312、GBK、JIS等等）。</p>



<p>而 <strong>Unicode Character Set </strong>就是Unicode字符集，<strong>一般是指 </strong><strong>UTF-16</strong><strong> 编码的 Unicode。</strong>也就是说每个字符编码为两个字节，两个字节可以表示65535个字符，65535个字符可以表示世界上大部分的语言。</p>



<p><strong>一般推荐使用Unicode的方式</strong>，因为它可以适应各个国家语言，在进行软件国际时将会非常便得。除非在对占用存储空间的大小要求非常高的时候，或要兼容C的代码时，我们才会使用多字节的方式 。</p>



<p><strong>理解 _T()、_Text() 宏即 L”“</strong></p>



<p><a href="https://www.lmonkey.com/t/DExgl2ZBK">上一小节</a>对MessageBox的调用中除了使用L”Title”外，还可以使用_T(“Title”)和_TEXT(“Title”)。而且你会发现在MFC和Win32程序中会更多地使用_T和_TEXT，那_T、_TEXT和L之间有什么区别呢？</p>



<p>通过第一小节<a href="https://www.lmonkey.com/t/DExgl2ZBK">多字节字符与宽字节字符</a>我们知道表示多字节字符(char)串常量时用一般的双引号括起来就可以了，如”String test”；而表示宽字节字符(wchar_t)串常量时要在引号前加L，如L”String test”。</p>



<p>查看tchar.h头文件的定义我们知道_T和_TEXT的功能是一样的，是一个预定义的宏。</p>



<p>#define _T(x) __T(x) #define _TEXT(x) __T(x)</p>



<p>我们再看看__T(x)的定义，发现它有两个：</p>



<p>#ifdef _UNICODE // &#8230; 省略其它代码 #define __T(x) L ## x // &#8230; 省略其它代码 #else /* ndef _UNICODE */ // &#8230; 省略其它代码 #define __T(x) x // &#8230; 省略其它代码 #endif /* _UNICODE */</p>



<p>这下明白了吗？当我们的工程的Character Set设置为Use Unicode Character Set时_T和_TEXT就会在常量字符串前面加L，否则(即Use Multi-Byte Character Set时)就会以一般的字符串处理。</p>



<p><strong>Dword、LPSTR、LPWSTR、LPCSTR、LPCWSTR、LPTSTR、LPCTSTR</strong></p>



<p>VC++中还有一些常用的宏你也许会范糊涂，如Dword、LPSTR、LPWSTR、LPCSTR、LPCWSTR、LPTSTR、LPCTSTR。这里我们统一总结一下：&nbsp;</p>



<p><strong>常见的宏：</strong></p>



<figure class="wp-block-table"><table><tbody><tr><td>类型</td><td>MBCS</td><td>UNICODE</td></tr><tr><td>WCHAR</td><td>wchar_t</td><td>wchar_t</td></tr><tr><td>LPSTR</td><td>char*</td><td>char*</td></tr><tr><td>LPCSTR</td><td>const char*</td><td>const char*</td></tr><tr><td>LPWSTR</td><td>wchar_t*</td><td>wchar_t*</td></tr><tr><td>LPCWSTR</td><td>const wchar_t*</td><td>const wchar_t*</td></tr><tr><td>TCHAR</td><td>char</td><td>wchar_t</td></tr><tr><td>LPTSTR</td><td>TCHAR*(或char*)</td><td>TCHAR* (或wchar_t*)</td></tr><tr><td>LPCTSTR</td><td>const TCHAR*</td><td>const TCHAR*</td></tr></tbody></table></figure>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p><strong>相互转换方法：</strong></p>



<p>LPWSTR-&gt;LPTSTR: W2T();&nbsp;</p>



<p>LPTSTR-&gt;LPWSTR: T2W();&nbsp;</p>



<p>LPCWSTR-&gt;LPCSTR: W2CT();&nbsp;</p>



<p>LPCSTR-&gt;LPCWSTR: T2CW();</p>



<p>ANSI-&gt;UNICODE: A2W();&nbsp;</p>



<p>UNICODE-&gt;ANSI: W2A();</p>



<p><strong>字符串函数：</strong></p>



<p>还有一些字符串的操作函数，它们也有一 一对应关系：</p>



<figure class="wp-block-table"><table><tbody><tr><td>MBCS</td><td>UNICODE</td></tr><tr><td>strlen();</td><td>wcslen();</td></tr><tr><td>strcpy();</td><td>wcscpy();</td></tr><tr><td>strcmp();</td><td>wcscmp();</td></tr><tr><td>strcat();</td><td>wcscat();</td></tr><tr><td>strchr();</td><td>wcschr();</td></tr><tr><td>…</td><td>…</td></tr></tbody></table></figure>



<p>通过这些函数和宏的命名你也许就发现了一些霍规律，一般带有前缀w(或后缀W)的都是用于宽字符的，而不带前缀w(或带有后缀A)的一般是用于多字节字符的。</p>



<p><a href="https://www.lmonkey.com/t/DExgl2ZBK"></a><strong>理解CString产生的原因与工作的机理</strong></p>



<p>CString：动态的TCHAR数组，是对TCHAR数组的一种封闭。它是一个完全独立的类，封装了“+”等操作符和字符串操作方法，换句话说就是CString是对TCHAR操作的方法的集合。它的作用是方便WIN32程序和MFC程序进行字符串的处理和类型的转换。</p>



<p>关于CString更详细的用法请参考：&nbsp;</p>



<p><a href="https://www.lmonkey.com/t/DExgl2ZBK">CString与string、char*的区别和转换</a></p>



<p><a href="https://www.lmonkey.com/t/DExgl2ZBK">CString的常见用法</a></p>



<p>参考文章：&nbsp;</p>



<p><a href="https://www.lmonkey.com/t/DExgl2ZBK">字符集和字符编码（Charset &amp; Encoding）</a></p>



<p><a href="https://www.lmonkey.com/t/DExgl2ZBK">字符，字节和编码</a></p>



<p><a href="https://www.lmonkey.com/t/DExgl2ZBK">《windows核心编程系列》二谈谈ANSI和Unicode字符集</a></p>



<p><a href="https://www.lmonkey.com/t/DExgl2ZBK">Dword、LPSTR、LPWSTR、LPCSTR、LPCWSTR、LPTSTR、LPCTSTR</a></p>



<p>转自：<a href="http://blog.csdn.net/luoweifu/article/details/49382969">http://blog.csdn.net/luoweifu/article/details/49382969</a></p>



<p><a href="https://www.lmonkey.com/t/DExgl2ZBK">https://www.lmonkey.com/t/DExgl2ZBK</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.greatfar.cn/archives/22/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>c++ 中 . 和 箭头 的区别</title>
		<link>https://www.greatfar.cn/archives/20</link>
					<comments>https://www.greatfar.cn/archives/20#respond</comments>
		
		<dc:creator><![CDATA[Greatfar]]></dc:creator>
		<pubDate>Fri, 19 May 2023 12:18:59 +0000</pubDate>
				<category><![CDATA[note]]></category>
		<guid isPermaLink="false">http://www.greatfar.cn/?p=20</guid>

					<description><![CDATA[c++ 中 . 和 -&#62; 的区别是什么？ -&#62; 主要用于&#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p>c++ 中 . 和 -&gt; 的区别是什么？</p>



<p>-&gt; 主要用于<strong>自定义类</strong><strong>类型</strong>的<strong>指针</strong>访问类的成员，</p>



<p>. 运算符主要用于类的<strong>对象</strong>访问类的成员，或者，结构体变量访问结构体的成员。</p>



<p>class A { public: int a; } A ma; // 声明一个对象，在栈中 A * p = new A; // 堆中创建对象，需要自行delete ma.a = 10; p-&gt;a = 20;</p>



<p>指针p应使用 -&gt; 来访问成员a，比如p-&gt;a</p>



<p>而 ma 应使用 . 来访问，比如ma.a</p>



<p>区别：凡是<strong>指针</strong>就全使用 -&gt; ，<strong>对象</strong>就使用 . 运算符</p>



<p>类的 <strong>this 是指针</strong>，所以也用 -&gt;</p>



<p><a href="http://t.zoukankan.com/Renyi-Fan-p-12961980.html">http://t.zoukankan.com/Renyi-Fan-p-12961980.html</a></p>



<p>this指针是C++实现封装的一种机制，它将对象和对象调用的非静态成员函数联系在一起，从外部看来，每个对象都拥有自己的成员函数。当创建一个对象时，编译器会初始化一个this指针，指向创建的对象，this指针并不存储在对象内部，而是作为所有非静态成员函数的参数。</p>



<p><a href="http://c.biancheng.net/view/66.html">http://c.biancheng.net/view/66.html</a></p>



<p>在 C/C++ 语言中，使用 new 实例化类型后，访问其成员变量时必须使用-&gt;操作符</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.greatfar.cn/archives/20/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>C语言中char数组和char指针有什么区别？</title>
		<link>https://www.greatfar.cn/archives/17</link>
					<comments>https://www.greatfar.cn/archives/17#respond</comments>
		
		<dc:creator><![CDATA[Greatfar]]></dc:creator>
		<pubDate>Fri, 19 May 2023 12:18:22 +0000</pubDate>
				<category><![CDATA[note]]></category>
		<guid isPermaLink="false">http://www.greatfar.cn/?p=17</guid>

					<description><![CDATA[首先，让我们通过下面的例子，来了解一下C语言中字符数组和字符指针之间&#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p>首先，让我们通过下面的例子，来了解一下C语言中字符数组和字符指针之间的区别。</p>



<p>void test() { //arr is array of characters char arr[12] = &#8220;Aticleworld&#8221;; //ptr is pointer to char char *ptr = &#8220;Aticleworld&#8221;; }</p>



<p>下面，让我们比较一下arr（字符数组）和ptr（字符指针）。</p>



<p>区别1：</p>



<p>字符串文本是用双引号括起来的零个或多个多字节字符的序列。当你编写语句 char arr[12] = &#8220;Aticleworld&#8221; 时，字符串文本中的字符被复制到 arr。 当您编写语句 char *ptr = &#8220;Aticleworld&#8221; 时，您是让字符串文本数组进行数组到指针的转换，以获取指向其第一个元素的指针。指针 ptr 指向字符串文本数组的第一个元素（&#8217;A&#8217;)。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="808" height="369" src="https://www.greatfar.cn/wp-content/uploads/2023/05/image-1.png" alt="" class="wp-image-18" srcset="https://www.greatfar.cn/wp-content/uploads/2023/05/image-1.png 808w, https://www.greatfar.cn/wp-content/uploads/2023/05/image-1-300x137.png 300w, https://www.greatfar.cn/wp-content/uploads/2023/05/image-1-768x351.png 768w" sizes="(max-width: 808px) 100vw, 808px" /></figure>



<p>区别2：</p>



<p>arr 是存储在连续内存位置的字符集合，而 ptr 保存字符的地址。 arr 包含 12 个元素，每个元素位于连续的内存位置。另一方面，ptr 保存字符串文本的第一个字符的地址。</p>



<p>区别3：</p>



<p>当我们在 char 数组 arr 上使用 sizeof 运算符时，它给出字符总数，而 char 指针 ptr 只给出指针的大小。例如：</p>



<p>#include &lt;stdio.h&gt; int main() { //arr is array of characters char arr[] = &#8220;Aticleworld&#8221;; //ptr is pointer to char char *ptr = &#8220;Aticleworld&#8221;; printf(&#8220;Size of arr %ld\n&#8221;, sizeof(arr)); // sizeof a pointer is printed which is same for all type // of pointers (char *, void *, etc) printf(&#8220;Size of ptr %ld&#8221;, sizeof(ptr)); return 0; }</p>



<p>输出：</p>



<p>Size of arr 24 Size of ptr 4</p>



<p>区别4： 和指针之间的另一个重要区别是，我们可以增加指针，但不能创建数组的增量。例如：</p>



<p>arr++ =&gt;非法语句。 ptr++ ==&gt;正常语句。</p>



<p>区别5： 我们可以将值重新分配给数组，但字符串文本不可修改。如果程序尝试修改由字符串文本形成的静态数组，则行为未定义。例如：</p>



<p>//arr is array of characters char arr[] = &#8220;Aticleworld&#8221;; gets(arr); fgets(arr,sizeof(arr),stdin); //有效表达式 scanf(&#8220;%s&#8221;, arr); //有效表达式 strcpy(arr, &#8220;aticle&#8221;); //有效表达式 arr[0] = &#8216;a&#8217;; //有效表达式 arr[10] = &#8216;M&#8217;; //有效表达式 arr[11] = &#8216;M&#8217;; //有效表达式</p>



<p>char *ptr = &#8220;Aticleworld&#8221;; ptr[0] = &#8216;P&#8217;; //无效表达式 *ptr = &#8216;W&#8217;; //无效表达式</p>



<p>区别6： 未初始化的指针，也可能导致未定义的行为。例如</p>



<p>char *ptr; ptr[0] = &#8216;A&#8217;; //未定义的行为 gets(ptr); //未定义的行为 scanf(“%s”， ptr); //未定义的行为 strcpy(ptr， “source”); //未定义的行为 strcat(ptr， “second string”); //未定</p>



<p>区别7：</p>



<p>Char数组本质上是静态的，这意味着您无法调整数组的大小，而使用指针，您可以在任何时间点更改分配的内存的大小。</p>



<p>区别8：</p>



<p>数组完全由程序控制。它将正确分配所需的内存，并在超出范围时自动释放内存。但是，如果分配动态内存，则char指针的情况有所不同，您必须手动取消分配它，否则会引入内存泄漏。例如：</p>



<p>void foo1() { //arr是字符数组 char arr[12] = &#8220;Aticleworld&#8221;; } //Issue memory leak void foo2() { char *ptr = (char*)malloc(12); //忘记释放内存 }</p>



<p><a href="https://mp.weixin.qq.com/s/aIIRxa7B6eRijl2LZRS1Fg">https://mp.weixin.qq.com/s/aIIRxa7B6eRijl2LZRS1Fg</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.greatfar.cn/archives/17/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Java 包装类、装箱和拆箱</title>
		<link>https://www.greatfar.cn/archives/15</link>
					<comments>https://www.greatfar.cn/archives/15#respond</comments>
		
		<dc:creator><![CDATA[Greatfar]]></dc:creator>
		<pubDate>Fri, 19 May 2023 12:17:44 +0000</pubDate>
				<category><![CDATA[note]]></category>
		<guid isPermaLink="false">http://www.greatfar.cn/?p=15</guid>

					<description><![CDATA[在&#160;Java&#160;的设计中提倡一种思想，即一切皆对象&#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p>在&nbsp;<a href="http://c.biancheng.net/java/">Java</a>&nbsp;的设计中提倡一种思想，即一切皆对象。但是从数据类型的划分中，我们知道 Java 中的数据类型分为基本数据类型和引用数据类型，但是基本数据类型怎么能够称为对象呢？于是 Java 为每种基本数据类型分别设计了对应的类，称之为包装类（Wrapper Classes），也有地方称为外覆类或数据类型类。</p>



<p>包装类和基本数据类型的关系如下表所示。</p>



<figure class="wp-block-table"><table><tbody><tr><td>序号</td><td>基本数据类型</td><td>包装类</td></tr><tr><td>1</td><td>byte</td><td>Byte</td></tr><tr><td>2</td><td>short</td><td>Short</td></tr><tr><td>3</td><td>int</td><td>Integer</td></tr><tr><td>4</td><td>long</td><td>Long</td></tr><tr><td>5</td><td>char</td><td>Character</td></tr><tr><td>6</td><td>float</td><td>Float</td></tr><tr><td>7</td><td>double</td><td>Double</td></tr><tr><td>8</td><td>boolean</td><td>Boolean</td></tr></tbody></table></figure>



<p>从上表中我们可以看出，除了 Integer 和 Character 定义的名称与基本数据类型定义的名称相差较大外，其它的 6 种类型的名称都是很好掌握的。</p>



<p><strong>装箱和拆箱</strong></p>



<p>在了解包装类后，下面介绍包装类的装箱与拆箱的概念。其实这两个概念本身并不难理解，基本数据类型转换为包装类的过程称为装箱，例如把 int 包装成 Integer 类的对象；包装类变为基本数据类型的过程称为拆箱，例如把 Integer 类的对象重新简化为 int。</p>



<p>手动实例化一个包装类称为手动拆箱装箱。Java 1.5 版本之前必须手动拆箱装箱，之后可以自动拆箱装箱，也就是在进行基本数据类型和对应的包装类转换时，系统将自动进行装箱及拆箱操作，不用在进行手工操作，为开发者提供了更多的方便。例如：</p>



<p>public class Demo { public static void main(String[] args) { int m = 500; Integer obj = m; // 自动装箱 int n = obj; // 自动拆箱 System.out.println(&#8220;n = &#8221; + n); Integer obj1 = 500; System.out.println(&#8220;obj等价于obj1返回结果为&#8221; + obj.equals(obj1)); } }</p>



<p>运行结果：</p>



<p>n = 500</p>



<p>obj等价于obj1返回结果为true</p>



<p>自动拆箱装箱是常用的一个功能，读者需要重点掌握。</p>



<p><strong>包装类的应用</strong></p>



<p>下面我们讲解 8 个包装类的使用，下面是常见的应用场景。</p>



<p><strong>1) 实现 int 和 Integer 的相互转换</strong></p>



<p>可以通过 Integer 类的构造方法将 int 装箱，通过 Integer 类的 intValue 方法将 Integer 拆箱。例如：</p>



<p>public class Demo { public static void main(String[] args) { int m = 500; Integer obj = new Integer(m); // 手动装箱 int n = obj.intValue(); // 手动拆箱 System.out.println(&#8220;n = &#8221; + n); Integer obj1 = new Integer(500); System.out.println(&#8220;obj等价于obj1的返回结果为&#8221; + obj.equals(obj1)); } }</p>



<p>运行结果：</p>



<p>n = 500</p>



<p>obj等价于obj1的返回结果为true</p>



<p><strong>2) 将字符串转换为数值类型</strong><strong>在 Integer 和 Float 类中分别提供了以下两种方法：</strong></p>



<p>① Integer 类（String 转 int 型）</p>



<p>int parseInt(String s);</p>



<p>s 为要转换的字符串。</p>



<p>② Float 类（String 转 float 型）</p>



<p>float parseFloat(String s)</p>



<p>注意：使用以上两种方法时，字符串中的数据必须由数字组成，否则转换时会出现程序错误。</p>



<p>下面为字符串转换为数值类型的例子：</p>



<p>public class Demo { public static void main(String[] args) { String str1 = &#8220;30&#8221;; String str2 = &#8220;30.3&#8221;; // 将字符串变为int型 int x = Integer.parseInt(str1); // 将字符串变为float型 float f = Float.parseFloat(str2); System.out.println(&#8220;x = &#8221; + x + &#8220;；f = &#8221; + f); } }</p>



<p>运行结果：</p>



<p>x = 30；f = 30.3</p>



<p><strong>3) 将整数转换为字符串</strong></p>



<p>Integer 类有一个静态的 toString() 方法，可以将整数转换为字符串。例如：</p>



<p>public class Demo { public static void main(String[] args) { int m = 500; String s = Integer.toString(m); System.out.println(&#8220;s = &#8221; + s); } }</p>



<p>运行结果：</p>



<p>s = 500</p>



<p><a href="http://c.biancheng.net/view/6119.html">http://c.biancheng.net/view/6119.html</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.greatfar.cn/archives/15/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Java Iterator（迭代器）遍历Collection集合元素</title>
		<link>https://www.greatfar.cn/archives/13</link>
					<comments>https://www.greatfar.cn/archives/13#respond</comments>
		
		<dc:creator><![CDATA[Greatfar]]></dc:creator>
		<pubDate>Fri, 19 May 2023 12:16:50 +0000</pubDate>
				<category><![CDATA[note]]></category>
		<guid isPermaLink="false">http://www.greatfar.cn/?p=13</guid>

					<description><![CDATA[Iterator（迭代器）是一个接口，它的作用就是遍历容器的所有元素&#46;&#46;&#46;]]></description>
										<content:encoded><![CDATA[
<p>Iterator（迭代器）是一个接口，它的作用就是遍历容器的所有元素，也是&nbsp;<a href="http://c.biancheng.net/java/">Java</a>&nbsp;集合框架的成员，但它与 Collection 和 Map 系列的集合不一样，Collection 和 Map 系列集合主要用于盛装其他对象，而 Iterator 则主要用于遍历（即迭代访问）Collection 集合中的元素。</p>



<p>Iterator 接口隐藏了各种 Collection 实现类的底层细节，向应用程序提供了遍历 Collection 集合元素的统一编程接口。Iterator 接口里定义了如下 4 个方法。</p>



<ul>
<li>boolean hasNext()：如果被迭代的集合元素还没有被遍历完，则返回 true。</li>



<li>Object next()：返回集合里的下一个元素。</li>



<li>void remove()：删除集合里上一次 next 方法返回的元素。</li>



<li>void forEachRemaining(Consumer action)：这是 Java 8 为 Iterator 新增的默认方法，该方法可使用 Lambda 表达式来遍历集合元素。</li>
</ul>



<p>下面程序示范了通过 Iterator 接口来遍历集合元素。</p>



<p>import java.util.Collection; import java.util.HashSet; import java.util.Iterator; public class IteratorTest { public static void main(String[] args) { // 创建一个集合 Collection objs = new HashSet(); objs.add(&#8220;C语言中文网Java教程&#8221;); objs.add(&#8220;C语言中文网C语言教程&#8221;); objs.add(&#8220;C语言中文网C++教程&#8221;); // 调用forEach()方法遍历集合 // 获取books集合对应的迭代器 Iterator it = objs.iterator(); while (it.hasNext()) { // it.next()方法返回的数据类型是Object类型，因此需要强制类型转换 String obj = (String) it.next(); System.out.println(obj); if (obj.equals(&#8220;C语言中文网C语言教程&#8221;)) { // 从集合中删除上一次next()方法返回的元素 it.remove(); } // 对book变量赋值，不会改变集合元素本身 obj = &#8220;C语言中文网Python语言教程&#8221;; } System.out.println(objs); } }</p>



<p>从上面代码中可以看出，Iterator 仅用于遍历集合，如果需要创建 Iterator 对象，则必须有一个被迭代的集合。没有集合的 Iterator 没有存在的价值。</p>



<p>注意：Iterator 必须依附于 Collection 对象，若有一个 Iterator 对象，则必然有一个与之关联的 Collection 对象。Iterator 提供了两个方法来迭代访问 Collection 集合里的元素，并可通过 remove() 方法来删除集合中上一次 next() 方法返回的集合元素。</p>



<p>上面程序中第 24 行代码对迭代变量 obj 进行赋值，但当再次输岀 objs 集合时，会看到集合里的元素没有任何改变。所以当使用 Iterator 对集合元素进行迭代时，Iterator 并不是把集合元素本身传给了迭代变量，而是把集合元素的值传给了迭代变量，所以修改迭代变量的值对集合元素本身没有任何影响。</p>



<p>当使用 Iterator 迭代访问 Collection 集合元素时，Collection 集合里的元素不能被改变，只有通过 Iterator 的 remove() 方法删除上一次 next() 方法返回的集合元素才可以，否则将会引发“java.util.ConcurrentModificationException”异常。下面程序示范了这一点。</p>



<p>public class IteratorErrorTest { public static void main(String[] args) { // 创建一个集合 Collection objs = new HashSet(); objs.add(&#8220;C语言中文网Java教程&#8221;); objs.add(&#8220;C语言中文网C语言教程&#8221;); objs.add(&#8220;C语言中文网C++教程&#8221;); // 获取books集合对应的迭代器 Iterator it = objs.iterator(); while (it.hasNext()) { String obj = (String) it.next(); System.out.println(obj); if (obj.equals(&#8220;C语言中文网C++教程&#8221;)) { // 使用Iterator迭代过程中，不可修改集合元素，下面代码引发异常 objs.remove(obj); } } } }</p>



<p>输出结果为：</p>



<p>C语言中文网C++教程</p>



<p>Exception in thread &#8220;main&#8221; java.util.ConcurrentModificationException</p>



<p>at java.util.HashMap$HashIterator.nextNode(Unknown Source)</p>



<p>at java.util.HashMap$KeyIterator.next(Unknown Source)</p>



<p>at IteratorErrorTest.main(IteratorErrorTest.java:15)</p>



<p>上面程序中第 15 行代码位于 Iterator 迭代块内，也就是在 Iterator 迭代 Collection 集合过程中修改了 Collection 集合，所以程序将在运行时引发异常。</p>



<p>Iterator 迭代器采用的是快速失败（fail-fast）机制，一旦在迭代过程中检测到该集合已经被修改（通常是程序中的其他线程修改），程序立即引发 ConcurrentModificationException 异常，而不是显示修改后的结果，这样可以避免共享资源而引发的潜在问题。</p>



<p>快速失败（fail-fast）机制，是 Java Collection 集合中的一种错误检测机制。</p>



<p>注意：上面程序如果改为删除“C语言中文网C语言教程”字符串，则不会引发异常。这样可能有些读者会“心存侥幸”地想，在迭代时好像也可以删除集合元素啊。实际上这是一种危险的行为。对于 HashSet 以及后面的 ArrayList 等，迭代时删除元素都会导致异常。只有在删除集合中的某个特定元素时才不会抛出异常，这是由集合类的实现代码决定的，程序员不应该这么做。</p>



<p><a href="http://c.biancheng.net/view/6795.html">http://c.biancheng.net/view/6795.html</a></p>



<p><strong>Java使用Lambda表达式遍历Iterator迭代器</strong></p>



<p><a href="http://c.biancheng.net/java/">Java</a>&nbsp;8 为 Iterator 引入了一个 forEachRemaining(Consumer action) 默认方法，该方法所需的 Consumer 参数同样也是函数式接口。当程序调用 Iterator 的 forEachRemaining(Consumer action) 遍历集合元素时，程序会依次将集合元素传给 Consumer 的 accept(T t) 方法（该接口中唯一的抽象方法）。</p>



<p>java.util.function 中的 Function、Supplier、Consumer、Predicate 和其他函数式接口被广泛用在支持 Lambda 表达式的 API 中。“void accept(T t);”是 Consumer 的核心方法，用来对给定的参数 T 执行定义操作。</p>



<p>如下程序示范了使用 Lambda 表达式来遍历集合元素。</p>



<p>public class IteratorEach { public static void main(String[] args) { // 创建一个集合 Collection objs = new HashSet(); objs.add(&#8220;C语言中文网Java教程&#8221;); objs.add(&#8220;C语言中文网C语言教程&#8221;); objs.add(&#8220;C语言中文网C++教程&#8221;); // 获取objs集合对应的迭代器 Iterator it = objs.iterator(); // 使用Lambda表达式（目标类型是Comsumer）来遍历集合元素 it.forEachRemaining(obj -&gt; System.out.println(&#8220;迭代集合元素：&#8221; + obj)); } }</p>



<p>输出结果为：</p>



<p>迭代集合元素：C语言中文网C++教程</p>



<p>迭代集合元素：C语言中文网C语言教程</p>



<p>迭代集合元素：C语言中文网Java教程</p>



<p>面程序中第 11 行代码调用了 Iterator 的 forEachRemaining() 方法来遍历集合元素，传给该方法的参数是一个 Lambda 表达式，该 Lambda 表达式的目标类型是 Consumer，因此上面代码也可用于遍历集合元素。</p>



<p><a href="http://c.biancheng.net/view/6797.html">http://c.biancheng.net/view/6797.html</a></p>



<p><strong>Java使用foreach循环遍历Collection集合</strong></p>



<p>《Java Iterator遍历Collection集合元素》一节中主要讲解如何使用 Iterator 接口迭代访问 Collection 集合里的元素，除了这个方法之外，我们还可以使用 Java 5 提供的 foreach 循环迭代访问集合元素，而且更加便捷。如下程序示范了使用 foreach 循环来迭代访问集合元素。</p>



<p>public class ForeachTest { public static void main(String[] args) { // 创建一个集合 Collection objs = new HashSet(); objs.add(&#8220;C语言中文网Java教程&#8221;); objs.add(&#8220;C语言中文网C语言教程&#8221;); objs.add(&#8220;C语言中文网C++教程&#8221;); for (Object obj : objs) { // 此处的obj变量也不是集合元素本身 String obj1 = (String) obj; System.out.println(obj1); if (obj1.equals(&#8220;C语言中文网Java教程&#8221;)) { // 下面代码会引发 ConcurrentModificationException 异常 objs.remove(obj); } } System.out.println(objs); } }</p>



<p>输出结果为：</p>



<p>C语言中文网C++教程</p>



<p>C语言中文网C语言教程</p>



<p>C语言中文网Java教程</p>



<p>[C语言中文网C++教程, C语言中文网C语言教程]</p>



<p>上面代码使用 foreach 循环来迭代访问 Collection 集合里的元素更加简洁，这正是 JDK 1.5 的 foreach 循环带来的优势。与使用 Iterator 接口迭代访问集合元素类似的是，foreach 循环中的迭代变量也不是集合元素本身，系统只是依次把集合元素的值赋给迭代变量，因此在 foreach 循环中修改迭代变量的值也没有任何实际意义。</p>



<p>同样，当使用 foreach 循环迭代访问集合元素时，该集合也不能被改变，否则将引发 ConcurrentModificationException 异常。所以上面程序中第 14 行代码处将引发该异常。</p>



<p><a href="http://c.biancheng.net/view/6798.html">http://c.biancheng.net/view/6798.html</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.greatfar.cn/archives/13/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
