存档

‘C/C++’ 分类的存档

c++指针和引用的区别

2020年6月10日 评论已被关闭

c++中,指针和引用都是显著提升代码运行效率的特性,很多情况下,两者都可以使用。

其主要区别在于:

  1. 本质上,指针是一个对象,有自己的地址,存放其他对象的地址;而引用只是其他对象的别名,不是一个对象,编译器不为其分配内存空间。
  2. 动态分配内存时,只能用指针保存内存的地址
  3. 不考虑const情形,指针初始化后可以再次指向另一个同类型对象;引用一旦初始化,其指向不再改变
  4. 指针为空值,即空指针nullptr;引用不能为空值,必须在初始化时设置其指向对象
  5. 指针可以进行增减运算,以指向和原来指向对象相邻的对象;引用一般不执行增减操作,如果执行,则是对其引用对象进行增减运算,前提是该对象需要支持增加运算
分类: C/C++, 开发 标签:

c++数组下标操作底层是通过指针算术计算实现的

2020年6月9日 评论已被关闭

在c/c++中,数组可以通过下标来存取其中的元素。

数组名可视为一个常量指针。

数组的下标操作在底层就是通过该指针的算术加减来实现的。即:

a[i] == *(&a[0]+i) == *(a+i) == *(i+a) == i[a]

看这个连续等式,后边两个写法是不是有点不淡定了?

没错!代码确实可以这样写。

#include <iostream>
using namespace std;

int main(){

	int a[] = {1,2,3,4};
	cout << a[2] << endl;

	cout << 2[a] << endl;
	return 0;
}

上边这个代码片段编译完全没有问题,而且运行结果也OK。
神奇吧!

不过这只是个小玩具,可不能用在实际项目中啊。

分类: C/C++, 开发 标签:

c++11特性:使用条件变量condition_variable进行线程间通信

2020年6月9日 评论已被关闭

多线程编程要实现的一个目的就是,线程间协同工作,以提高效率。

既然要协同,相互之间需要有一个通信机制,互通有无。

c++11提供了条件变量condition_variable来实现线程间通信。

线程可以通过条件变量来wait某个事件,其他线程准备好相关数据后,可通过notify唤醒正在等待的线程。

condition_variable需要和一个mutex关联使用。

请看示例代码:

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>
using namespace std;


queue<int> msglist;
mutex mmsg;
condition_variable cv;

void produce_msg(){
	for(auto i=0; i<100; i++){
		{
			unique_lock<mutex> lk{mmsg};
			msglist.push(i);
		}

		cout << "Producer put a message: " << i << endl;
		cv.notify_all();
	}
}

void consume_msg(const string& cname){
	while(true){
		{
			unique_lock<mutex> lk{mmsg};
			while(msglist.size() == 0){ //must be a loop
				cv.wait(lk);
			}

			cout << "Consumer-" << cname << " woke up." << endl;
			auto i = msglist.front();
			msglist.pop();
			cout << "Consumer-" << cname << " got message: " << i << endl;
		}
		this_thread::sleep_for(chrono::seconds{1});
	}
}

int main(){
	thread tp{produce_msg};
	thread tc1{consume_msg, "C1"};
	thread tc2{consume_msg, "C2"};

	tc1.join();
	tc2.join();

	tp.join();

	return 0;
}

我们在main中定义了三个线程,tp为生产者,tc1和tc2为消费者。
这三个线程共同存取队列msglist,所以需要通过互斥量mmsg进行同步。
消费者线程启动后,通过条件变量cv.wait()实现阻塞,等待新消息的产生。wait()会释放持有的互斥量mmsg,直到被唤醒时重新加锁。
生产者线程tp向msglist放入一个消息后,就释放互斥量,并通过条件变量cv.notify_all()唤醒两个消费者。
两个消费者被唤醒后,会对互斥量进行竞争性加锁,加锁成功者从队列msglist中取的消息,并短暂sleep,以便另一个消费者有机会获取消息。

需要注意,在consume_msg中wait()是置于一个循环中的。

这样做是为了避免多个多个消费者被同时唤醒时,第一个消费者将队列中的消息取出后,后边的消费者不加判断就去取消息,很可能会取空的现象。

这就是通过条件变量进行线程间通信的基本方法。

分类: C/C++, 开发 标签: ,

c++11特性:线程库thread的使用方法

2020年6月8日 评论已被关闭

多线程是提高程序吞吐量和响应时间的一个基本方法。

不同平台都提供了多线程支持,虽然实现原理和使用方法基本相同,但是由于接口不一致,应用程序需要对各线程库进行封装,以屏蔽平台接口差异,实现代码的跨平台运行。

c++11为我们提供了平台统一的多线程接口,有了它,我们可以方便地开发出跨平台的多线程应用程序。

c++11提供的线程对象包括

  • thread:创建一个线程对象,并提供线程控制接口
  • mutex 和 unique_lock:互斥量和锁,用于同步对共享数据的访问

我们通过示例来看一下线程库的基本用法。

注意:通过g++编译多线程程序时,需要指定-pthread参数。否则会报如下错误:

Enable multithreading to use std::thread: Operation not permitted

  • 简单的多线程:各行其事
    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <string>
    #include <chrono>
    using namespace std;
    
    //no lock
    void simple(const string& who){
    	for(auto i=0; i<100; i++){
    	      cout << who << " is counting: " << i << endl;
    	      this_thread::sleep_for(chrono::milliseconds(10));
    	}
    	cout << who << " count Done." << endl;
    }
    
    int main(){
    	thread t1{simple, "T1"};
    	thread t2{simple, "T2"};
    
    	t1.join();
    	t2.join();
    
    	return 0;
    }
  • 基本的同步操作:“同时”操作一个变量
    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <string>
    #include <chrono>
    using namespace std;
    
    mutex m0;
    int count=9000;
    void f0(){
    	for(auto i=0; i<100; i++){
    	    {
    			unique_lock<mutex> lk{m0};
    			cout << ++count << endl;
    	    }
    
    	    this_thread::sleep_for(chrono::milliseconds(10));
    	}
    }
    
    int main(){
    	thread ta{f0};
    	thread tb{f0};
    
    	ta.join();
    	tb.join();
    
    	return 0;
    }
  • 死锁:
    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <string>
    #include <chrono>
    using namespace std;
    
    //dead lock
    mutex m1, m2;
    
    void f1(const string& tname){
    	unique_lock<mutex> lk1{m1};
    	this_thread::sleep_for(chrono::seconds{5});
    	cout << "Thread " << tname << " woke up." << endl;
    
    	unique_lock<mutex> lk2{m2};
    	cout << "Thread " << tname << " Done." << endl;
    }
    
    void f2(const string& tname){
    	unique_lock<mutex> lk2{m2};
    	this_thread::sleep_for(chrono::seconds{5});
    	cout << "Thread " << tname << " woke up." << endl;
    
    	unique_lock<mutex> lk1{m1};
    	cout << "Thread " << tname << " Done." << endl;
    }
    
    int main(){
    	thread t3{f1, "T3"};
    	thread t4{f2, "T4"};
    	
    	t3.join();
    	t4.join();
    
    	return 0;
    }
  • 避免死锁:一次性获取多个互斥量
    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <string>
    #include <chrono>
    using namespace std;
    
    //multiple lock
    mutex m3, m4;
    
    void f3(const string& tname, int s){
    	unique_lock<mutex> lk3{m3, defer_lock};
    	unique_lock<mutex> lk4{m4, defer_lock};
    
    	lock(lk3, lk4);
    	this_thread::sleep_for(chrono::seconds{s});
    	cout << "Thread--" << tname << " has waken up." << endl;
    }
    
    int main(){
    	thread t5{f3, "T5", 6};
    	thread t6{f3, "T6", 10};
    
    	t5.join();
    	t6.join();
    
    	return 0;
    }
分类: C/C++, 开发 标签: ,

c++流迭代器(stream iterator)读写文件内容并去重的方法

2020年6月7日 评论已被关闭

有一个文本文件words.txt,其内容如下:

hello
hello
what
is
your
name
hello

每行一个单词,单词可重复。我们需要将其中的单词去重并排序。

基本做法是,打开这个文件,逐行读取其中的单词,将这些单词去重并排序,再写入到结果文件中。

读写文件可以使用fstream,单词的去重及排序可以通过set实现。

c++的强大之处在于,可以通过STL将两者结合起来。

请看代码:

#include <iostream>
#include <fstream>
#include <iterator>
#include <string>
#include <set>
#include <algorithm>
using namespace std;

int main(){
  ifstream is("words.txt");
  ofstream os("result.txt");

  istream_iterator<string> iib(is), iie;
  set<string> b(iib, iie);
  cout << b.size() << endl;
  copy(b.begin(), b.end(), ostream_iterator<string>(os, "\n"));

  return !is.eof() || !os;
}

我们可以看到:
首先,使用istream_iterator构造了两个输入流迭代器,用于指向输入文件开头和结尾。
然后,通过这两个迭代器构造了一个set对象,从而实现了文件的读取操作;同时也实现了单词的去重和排序。
最后,构造一个输出流迭代器,并通过copy算法将set中的元素复制到输出流迭代器,从而实现了文件的写入操作。

堪称完美!

分类: C/C++, 开发 标签:

c++11特性:统一初始化列表

2020年6月7日 评论已被关闭

在c++98标准中,对不同类型的变量有着不同的初始化方式。

比如:

#include <vector>
using namespace std;

class CTest{
  public:
    CTest():m_i(10){}
    CTest(int i):m_i(i){}

  private:
    int m_i;
};

int main(){
    int i=2020;
    int j=20.2;
    int a[5]={1,2,3,4,5};
    
    char sz[10]={"hello"};

    vector<int> va, vb(10, 8);

    CTest ta, tb(10);

    return 0;

}

上边这个例子就可以看到至少有3中初始化方式:=、{}和函数调用()。
而且对j的初始化,还发生了隐式类型转换,确切地说是把20.2给截断了。

c++11为变量提供了统一的初始化方式:初始化器列表,即{}。

我们可以用{}语法来初始化所有类型的变量。比如:

#include <vector>
#include <iostream>
using namespace std;

class CTest{
  public:
    CTest():m_i(10){}
    CTest(int i):m_i(i){}

  private:
    int m_i;
};

int main(){
    int i{2020};
    int j{20.2}; //compile warning here
    int a[5]={1,2,3,4,5};
    
    char sz[10]={"hello"};

    vector<int> va, vb(10, 8);

    CTest ta{}, tb{10};
    
    cout << j << endl;

    return 0;
}

一个{}初始化所有类型变量,统一起来更方便。
而且对于类型的窄化转换还会给出告警,甚至报错,减少bug的产生。

这是提升开发效率的一个重要改善。

ps:c++11支持4种形式的初始化器,如下所示:

T a{v};     //优先考虑
T a={v};   //多于的=
T a=v;      //最传统的内置数据类型初始化方式
T a(v);      //用户自定义类型,如struct、class初始化方式
分类: C/C++, 开发 标签: ,