c++11新特性4(线程库的详细使用)

目录

1. pthread 和 thread的区别

2. thread的使用

2.1 线程函数

2.2 线程函数参数

2.3 join与detach:为了回收资源

3. 原子性操作库(atomic)

4. 互斥和同步

4.1 互斥量(互斥锁)

4.1.1 std::mutex

4.1.2 std::recursive_mutex

4.1.3 std::timed_mutex

4.1.4 std::recursive_timed_mutex

4.2 条件变量(同步)

5. lock_guard 和 unique_lock(使用以及底层探索)

5.1 lock_guard

5.2 unique_lock

6. 应用例题:

6.1 创建两个线程交替打印1,2...100

 6.2 交替打印数字和字符

7. 补充:并发与并行的区别?


多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。一般情况下,两种类型的多任务处理:基于进程和基于线程。

  • 基于进程的多任务处理是程序的并发执行。
  • 基于线程的多任务处理是同一程序的片段的并发执行。

多线程程序包含可以同时运行的两个或多个部分。这样的程序中的每个部分称为一个线程,每个线程定义了一个单独的执行路径。

Linux 操作系统,我们要使用 POSIX 编写多线程 C++ 程序。POSIX Threads 或 Pthreads 提供的 API 可在多种类 Unix POSIX 系统上可用,比如 FreeBSD、NetBSD、GNU/Linux、Mac OS X 和 Solaris。

在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差。C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的线程,必须包含< thread >头文件。

  • 线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。
  • 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。

1. pthread 和 thread的区别

std::thread是C++11接口,pthread是C++98接口且只支持Linux。C++的thread是经过良好设计并且跨平台的线程表示方式,然而pthread是“粗犷、直接、暴力”的类UNIX平台线程表示方式,如你在C++11的thread你可以使用lock_guard等来实现RAII方式的lock管理,而pthread则很难。

  • pthreadlinux下的多线程API头文件pthread.h

  • std::thread是c++标准库中的线程库,其意义在于可以跨平台不改代码

  1. std::thread配合lambda表达式创建个线程运行,很方便!
  2. thread对象直接join或者detach,很方便!
  3. 使用thread再配合mutex的std::unique_lock和std::lock_guard使用,很方便!
  4. 使用thread再配合条件变量使用,很方便!
  5. 使用std::this_thread::sleep_for(xxx)休眠某段时间,很方便!

2. thread的使用

thread是一个类,可以通过创建类对象来创建一个新线程,并通过初始化来关联一个可执行对象,使线程完成特定的工作。

1. thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个线程对象关联线程的状态转移给其他线程对象,转移期间不影响线程的执行。
2. 可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效

  • 采用无参构造函数构造的线程对象
  • 线程对象的状态已经转移给其他线程对象
  • 线程已经调用jion或者detach结束
 int main()
 {
	 size_t n = 100;
	 thread t1([n]{
		 for (size_t i = 0; i < n; i += 2){
			 cout << i << endl;}
	 });

	 thread t2([n]{
		 for (size_t i = 1; i < n; i += 2){
			 cout << i << endl;}
	 });
	 t1.join();
	 t2.join();
	 return 0;
 }

2.1 线程函数

当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。线程函数一般情况下可按照以下三种方式提供:

  • 函数指针
  • lambda表达式
  • 函数对象
void ThreadFunc(int a)
{
	cout << "Thread1" << a << endl;
}

class TF
{
public:
	void operator()()
	{
		cout << "Thread3" << endl;
	}
};

int main()
{
	// 线程函数为函数指针
	thread t1(ThreadFunc, 10);

	// 线程函数为lambda表达式
	thread t2([]{cout << "Thread2" << endl; });

	// 线程函数为函数对象
	TF tf;
	thread t3(tf);

	t1.join();
	t2.join();
	t3.join();
	cout << "Main thread!" << endl;
	return 0;
}

2.2 线程函数参数

线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此:即使线程参数为引用类型,在线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参。

void ThreadFunc1(int& x){
	x += 10;}

void ThreadFunc2(int* x){
	*x += 10;}

int main()
{
	int a = 10;
	//在线程函数中对a修改,不会影响外部实参,
    //因为:线程函数参数虽然是引用方式,但其实际引用的是线程栈中的拷贝
	thread t1(ThreadFunc1, a);
	t1.join();
	cout << a << endl;

	// 如果想要通过形参改变外部实参时,必须借助std::ref()函数
	thread t2(ThreadFunc1, std::ref(a));
	t2.join();
	cout << a << endl;

	// 地址的拷贝
	thread t3(ThreadFunc2, &a);
	t3.join();
	cout << a << endl;
}

2.3 join与detach:为了回收资源

启动了一个线程后,当这个线程结束的时候,如何去回收线程所使用的资源呢?thread库给我们两种选择:

(1)join()方式:
        主线程被阻塞,当新线程终止时,join()会清理相关的线程资源,然后返回,主线程再继续向下执行,然后销毁线程对象。由于join()清理了线程的相关资源,thread对象与已销毁的线程就没有关系了,因此一个线程对象只能使用一次join(),否则程序会崩溃。

        但是,采用jion()方式结束线程时,jion()的调用位置非常关键,有可能发生函数返回了join没有调用,比如下面代码。为了避免该问题,可以采用RAII的方式对线程对象进行封装,比如

(2)detach()方式
        该函数被调用后,新线程与线程对象分离,不再被线程对象所表达,就不能通过线程对象控制线程了,新线程会在后台运行,其所有权和控制权将会交给c++运行库。同时,C++运行库保证,当线程退出时,其相关资源的能够正确的回收。

总结:线程对象销毁前,要么以jion()的方式等待线程结束,要么以detach()的方式将线程与线程对象分离

// jion()的误用一
void ThreadFunc() { cout<<"ThreadFunc()"<<endl; }
bool DoSomething() { return false; }
int main()
{
    std::thread t(ThreadFunc);
    if(!DoSomething())
        return -1;
    
    t.join();
    return 0;
}
//说明:如果DoSomething()函数返回false,主线程将会结束,jion()没有调用,
//线程资源没有回收,造成资源泄漏。
 
// jion()的误用二
void ThreadFunc() { cout<<"ThreadFunc()"<<endl; }
void Test1() { throw 1; }
void Test2()
{
    int* p = new int[10];
    std::thread t(ThreadFunc);
    try
    {
        Test1();
    }
    catch(...)
    {
        delete[] p;
        throw;
    }
    t.jion();
}

3. 原子性操作库(atomic)

多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的,那么没问题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦。比如

std::mutex m;
unsigned long sum = 0;
 
void fun(size_t num)
{
    for (size_t i = 0; i < num; ++i)
    {
        m.lock();//加锁
        sum++;
        m.unlock();//解锁
    }
}
 
int main()
{
    cout << "Before joining,sum = " << sum << std::endl;
    thread t1(fun, 10000000);
    thread t2(fun, 10000000);
    t1.join();
    t2.join();
    cout << "After joining,sum = " << sum << std::endl;
}

虽然加锁可以解决,但是加锁有一个缺陷就是:只要一个线程在对sum++时,其他线程就会被阻塞,会影响程序运行的效率,而且锁如果控制不好,还容易造成死锁。因此C++11中引入了原子操作。所谓原子操作:即不可被中断的一个或一系列操作,C++11引入的原子操作类型,使得线程间数据的同步变得非常高效。以上代码可以优化为:

atomic<long> sum = 0;//这个可以把对sum的操作(++或—)变成原子操作
//atomic_long sum = { 0 };//这样写也可以

void fun(size_t num)
{
	for (size_t i = 0; i < num; ++i)
		sum++;   // 这里对sum操作便是原子操作
}

int main()
{
	cout << "Before joining, sum = " << sum << std::endl;
	thread t1(fun, 1000000);
	thread t2(fun, 1000000);
	t1.join();
	t2.join();
	cout << "After joining, sum = " << sum << std::endl;
}

在C++11中,程序员不需要对原子类型变量进行加锁解锁操作,线程能够对原子类型变量互斥的访问。更为普遍的,程序员可以使用atomic类模板,定义出需要的任意原子类型。

atmoic<T> t;    // 声明一个类型为T的原子类型变量t

注意:原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝,因此在C++11中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及operator=等,为了防止意外,标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算符重载默认删除掉了。

#include <atomic>
int main()
{
    atomic<int> a1(0);
    //atomic<int> a2(a1);    // 编译失败
    atomic<int> a2(0);
    //a2 = a1;               // 编译失败
}

4. 互斥和同步

在多线程环境下,如果想要保证某个变量的安全性,只要将其设置成对应的原子类型即可,即高效又不容易出现死锁问题。但是有些情况下,我们可能需要保证一段代码的安全性,那么就只能通过锁的方式来进行控制。

4.1 互斥量(互斥锁)

多线程中对临界资源的访问会出现线程安全的问题,比如多个线程同时对一份资源进行写操作时,会造成数据混乱,因此引入了互斥的概念,即同一时刻只允许一个线程访问临界资源。互斥可以通过上锁和解锁来实现,在C++11中,Mutex总共包了四个互斥量的种类:

4.1.1 std::mutex

C++11提供的最基本的互斥量,该类的对象之间不能拷贝,也不能进行移动。mutex最常用的三个函数

4.1.2 std::recursive_mutex

其允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,释放互斥量时需要调用与该锁层次深度相同次数的 unlock(),除此之外,std::recursive_mutex 的特性和std::mutex 大致相同。

4.1.3 std::timed_mutex

比 std::mutex 多了两个成员函数,try_lock_for(),try_lock_until() 。

  • try_lock_for()
    接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与 std::mutex的 try_lock() 不同,try_lock 如果被调用时没有获得锁则直接返回 false),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。
  • try_lock_until()
    接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。

4.1.4 std::recursive_timed_mutex

4.2 条件变量(同步)

条件变量是为了实现进程间同步(让进程按照一定的次序进行),具体用法可参照下面的例题交替打印。

C++11 提供了条件变量condition variable用于实现线程间的同步操作,需要包含头文件#include <condition_variable>,并且一般都是与互斥量mutex配合使用。

条件变量的工作方式为:

  • 使用条件变量的线程首先对互斥量加锁
  • 检查某个条件,如果条件不满足,则释放互斥锁量,进入休眠;如果条件满足,则继续执行资源操作,执行完操作后释放互斥量
  • 另外一个线程加锁执行资源操作,使条件满足后,释放互斥量,唤醒等待的线程

5. lock_guard 和 unique_lock(使用以及底层探索)

直接使用mutex会有一些缺陷,锁控制不好时,可能会造成死锁,最常见的比如在锁中间代码返回,或者在锁的范围内抛异常,会造成资源浪费。因此:C++11采用RAII的方式对锁进行了封装,即lock_guardunique_lock

5.1 lock_guard

std::lock_gurad 是 C++11 中定义的模板类。定义如下:
template<class _Mutex>
class lock_guard
{
public:
    // 在构造lock_gard时,_Mtx还没有被上锁
    explicit lock_guard(_Mutex& _Mtx)
        : _MyMutex(_Mtx)
    {
        _MyMutex.lock();
    }
 
    // 在构造lock_gard时,_Mtx已经被上锁,此处不需要再上锁
    lock_guard(_Mutex& _Mtx, adopt_lock_t)
        : _MyMutex(_Mtx)
    {}

    // 在析构函数时,会自动解锁 
    ~lock_guard() _NOEXCEPT
    {
        _MyMutex.unlock();
    }
 
    lock_guard(const lock_guard&) = delete;
    lock_guard& operator=(const lock_guard&) = delete;
 
private:
    _Mutex& _MyMutex;
};

lock_guard类模板主要是通过RAII的方式,对其管理的互斥量进行了封装,在需要加锁的地方,只需要用上述介绍的任意互斥体实例化一个lock_guard,调用构造函数成功上锁,出作用域
前,lock_guard对象要被销毁,调用析构函数自动解锁,可以有效避免死锁问题。

    //无论是正常执行、还是中途返回、还是抛异常,都能保证这里锁一定解锁了   
    mutex mtx;
	lock_guard<mutex> lg(mtx);
    FILE* fout = fopen("test.txt", "r");
	if (fout == nullptr){
		// ....}
    
    //若只想让锁保护打开文件这段代码?可以定义一个匿名的局部域
    {
        mutex mtx;
	    lock_guard<mutex> lg(mtx);
        FILE* fout = fopen("test.txt", "r");
	    if (fout == nullptr){
		    // ....}
    }

5.2 unique_lock

互斥锁保证了线程间的同步,但是却将并行操作变成了串行操作,这对性能有很大的影响,所以我们要尽可能的 减小锁定的区域,也就是使用 细粒度锁
lock_guard的缺陷:太单一,用户没有办法对该锁进行控制,因此C++11又提供了unique_lock。
与lock_gard类似,使用互斥量实例化unique_lock的对象时,自动调用构造函数上锁,unique_lock对象销毁时自动调用析构函数解锁,可以很方便的防止死锁问题。
与lock_guard不同的是,unique_lock更加的灵活,提供了更多的成员函数:
上锁/解锁操作:lock、try_lock、try_lock_for、try_lock_until和unlock,可以更加灵活
下面看两端代码:
class LogFile {
    std::mutex _mu;
    ofstream f;
public:
    LogFile() {
        f.open("log.txt");
    }
    ~LogFile() {
        f.close();
    }
    void shared_print(string msg, int id) {
        {
            std::lock_guard<std::mutex> guard(_mu);
            //do something 1
        }
        //do something 2
        {
            std::lock_guard<std::mutex> guard(_mu);
            // do something 3
            f << msg << id << endl;
            cout << msg << id << endl;
        }
    }

};

上面的代码中,一个函数内部有两段代码需要进行保护,这个时候使用lock_guard就需要创建两个局部对象来管理同一个互斥锁(其实也可以只创建一个,但是锁的力度太大,效率不行),修改方法是使用unique_lock。它提供了lock()unlock()接口,能记录现在处于上锁还是没上锁状态,在析构的时候,会根据当前状态来决定是否要进行解锁(lock_guard就一定会解锁)。上面的代码修改如下:

class LogFile {
    std::mutex _mu;
    ofstream f;
public:
    LogFile() {
        f.open("log.txt");
    }
    ~LogFile() {
        f.close();
    }
    void shared_print(string msg, int id) {

        std::unique_lock<std::mutex> guard(_mu);
        //do something 1
        guard.unlock(); //临时解锁

        //do something 2

        guard.lock(); //继续上锁
        // do something 3
        f << msg << id << endl;
        cout << msg << id << endl;
        // 结束时析构guard会临时解锁
        // 这句话可要可不要,不写,析构的时候也会自动执行
        // guard.ulock();
    }

};

上面的代码可以看到,在无需加锁的操作时,可以先临时释放锁,然后需要继续保护的时候,可以继续上锁,这样就无需重复的实例化lock_guard对象,还能减少锁的区域。同样,可以使用std::defer_lock设置初始化的时候不进行默认的上锁操作:

void shared_print(string msg, int id) {
    std::unique_lock<std::mutex> guard(_mu, std::defer_lock);
    //do something 1

    guard.lock();
    // do something protected
    guard.unlock(); //临时解锁

    //do something 2

    guard.lock(); //继续上锁
    // do something 3
    f << msg << id << endl;
    cout << msg << id << endl;
    // 结束时析构guard会临时解锁
}

6. 应用例题:

6.1 创建两个线程交替打印1,2...100

//只使用互斥量
int main()
{
	int n = 100;
	mutex mtx;
	// 奇数 假设 thread2迟迟没创建好或者没有排到cpu时间片,就会导致t1连续打印奇数,不符合题意要求
	thread t1([&](){
		int i = 1;
		for (; i < n; i += 2){
			unique_lock<mutex> lock(mtx);
			cout << i << endl;}
	});
	// 偶数
	thread t2([&](){
		int j = 2;
		for (; j < n; j += 2){
			unique_lock<mutex> lock(mtx);
			cout << j << endl;}
	});
	t1.join();
	t2.join();
}

以上并不安全,有可能线程1打印了1,3,线程2才刚创建好,下面需要使用条件变量

//互斥量+条件变量
int main()
{
	int n = 100;
	mutex mtx;
	condition_variable cv;
	bool flag = true;

	// 奇数
	thread t1([&]() {
		int i = 1;
		while (i < n){
			unique_lock<mutex> lock(mtx);
			//wait的第二个参数为可执行对象,可以为lambda表达式
			cv.wait(lock, [&flag]()->bool {return flag; }); // true 则不会阻塞,继续执行,
			cout << i << endl;
			i += 2;
			flag = false;//通过flag来控制交替
			cv.notify_one();}//唤醒其他在等待的条件变量
		});

	// 偶数
	thread t2([&]() {
		int j = 2;
		while (j < n){
			unique_lock<mutex> lock(mtx);
			//wait的第二个参数为可执行对象,可以为lambda表达式
			cv.wait(lock, [&flag]()->bool {return !flag; });  // false 释放互斥量,并阻塞休眠(注意:wait的时候会释放锁,所以wait函数才需要一个参数是lock)
			cout << j << endl;
			j += 2;
			flag = true;
			cv.notify_one();}
		});

	t1.join();
	t2.join();
}

 6.2 交替打印数字和字符

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

mutex mtx;
condition_variable cv;
bool flag = true;

void print_num() {
    for (int i = 1; i <= 26; i++) {
        unique_lock<mutex> lock(mtx);
        while(!flag)
            cv.wait(lock);
        cout << i << endl;
        flag = false;
        cv.notify_one();
    }
}
void print_char() {
    for (char i = 'a'; i <= 'z'; i++) {
        unique_lock<mutex> lock(mtx);
        while(flag)
            cv.wait(lock);
        cout << i << endl;
        flag = true;
        cv.notify_one();
    }

}
int main() {
    thread p1(print_num);
    thread p2(print_char);
    p1.join();
    p2.join();
    return 0;
}

7. 补充:并发与并行的区别?

  1. 并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
  2. 并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
  3. 在一台处理器上“同时”(这个同时实际上市交替“”)处理多个任务,在多台处理器上同时处理多个任务
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自己购买的电子书, 现上传供大家下载!!! 《深入理解C++11:C++11新特性解析与应用》内容简介:国内首本全面深入解读C++11新标准的专著,由C++标准委员会代表和IBM XL编译器中国开发团队共同撰写。不仅详细阐述了C++11标准的设计原则,而且系统地讲解了C++11新标准中的所有新语言特性、新标准特性、对原有特性的改进,以及如何应用所有这些新特性。, 《深入理解C++11:C++11新特性解析与应用》一共8章:第1章从设计思维和应用范畴两个维度对C++11新标准中的所有特性进行了分类,呈现了C++11新特性的原貌;第2章讲解了在保证与C语言和旧版C++标准充分兼容的原则下增加的一些新特性;第3章讲解了具有广泛可用性、能与其他已有的或者新增的特性结合起来使用的、具有普适性的一些新特性;第4章讲解了C++11新标准对原有一些语言特性的改进,这些特性不仅能让C++变得更强大,还能提升程序员编写代码的效率;第5章讲解了C++11在安全方面所做的改进,主要涵盖枚举类型安全和指针安全两个方面的内容;第6章讲解了为了进一步提升和挖掘C++程序性能和让C++能更好地适应各 种新硬件的发展而设计的新特性,如多核、多线程、并行编程方面的新特性;第7章讲解了一些颠覆C++一贯设计思想的新特性,如lambda表达式等;第8章讲解了C++11为了解决C++编程中各种典型实际问题而做出的有效改进,如对Unicode的深入支持等。附录中则介绍了C++11标准与其他相关标准的兼容性和区别C++11中弃用的特性、编译器对C++11的支持情况,以及学习C++11的相关资源
### 回答1: C++11是C++标准的第11个版本,于2011年发布,引入了许多新特性和语言改进。下面是C++11的一些新特性: 1. 自动类型推导(auto) C++11中新增了auto关键字,可以用来自动推导变量的类型,从而简化代码。例如: ```cpp auto i = 10; // i的类型为int auto d = 3.14; // d的类型为double ``` 2. 统一的初始化语法 C++11中引入了统一的初始化语法,可以用一种方式来初始化任何类型的对象,包括内置类型、类类型和数组。例如: ```cpp int i{10}; // 使用大括号初始化int double d{3.14}; // 使用大括号初始化double std::string s{"hello"};// 使用大括号初始化std::string ``` 3. 委托构造函数 C++11中新增了委托构造函数,可以在一个构造函数中调用另一个构造函数,从而避免了代码重复。例如: ```cpp class Foo { public: Foo(int i) : Foo(i, 0) {} // 委托构造函数 Foo(int i, double d) : i_(i), d_(d) {} private: int i_; double d_; }; ``` 4. lambda表达式 C++11中新增了lambda表达式,可以方便地定义匿名函数,从而更加灵活地处理函数对象。例如: ```cpp std::vector<int> v = {1, 2, 3, 4, 5}; std::for_each(v.begin(), v.end(), [](int i){ std::cout << i << " "; }); // 使用lambda表达式打印vector中的元素 ``` 5. 默认函数 C++11中新增了默认函数,可以为函数的参数和类的构造函数设置默认值,从而简化代码。例如: ```cpp void foo(int i = 0, double d = 0.0); // 默认函数 class Bar { public: Bar(int i = 0, double d = 0.0); // 默认构造函数 }; ``` 6. 移动语义 C++11中引入了移动语义,可以将资源(如堆内存)从一个对象转移到另一个对象,从而避免了不必要的拷贝和销毁操作,提高了程序的效率。例如: ```cpp std::vector<int> v1 = {1, 2, 3, 4, 5}; std::vector<int> v2 = std::move(v1); // 使用移动语义将v1中的元素转移到v2中 ``` 7. 线程支持 C++11中新增了线程支持,可以方便地创建和管理多线程程序,从而提高程序的并发性能。例如: ```cpp #include <iostream> #include <thread> void hello() { std::cout << "Hello, world!" << std::endl; } int main() { std::thread t(hello); t.join(); return 0; } ``` 以上是C++11的一些新特性,这些新特性大大提高了C++的编程效率和程序性能。 ### 回答2: C语言11版是C语言的最新版本,在2011年发布。它引入了一些新的特性和改进,使得编写C语言程序更加方便和高效。 首先,C 11引入了一种叫做“_Thread_local”的关键字,可以用来声明线程本地存储。这使得在多线程编程中,每个线程都可以有自己独立的变量,而不会被其他线程影响。 其次,C 11还引入了复合字面量。这意味着可以在一个表达式中创建一个匿名的结构体、联合体或枚举类型,并直接使用它们,而不需要显式地在代码中定义这些类型。 另外,C 11还提供了对泛型编程的支持,通过引入"_Generic"关键字,可以根据参数的类型在编译时选择不同的代码分支。这使得在C语言中实现泛型算法变得更加容易。 此外,C 11还对语言的内存模型进行了一些改进。它引入了新的原子类型和操作,可以更方便地进行并发编程和线程同步。同时,C 11还为多线程编程提供了一套新的,包括原子操作、线程本地存储和线程间同步等功能。 最后,C 11还对语言的标准进行了一些改进。它引入了一些新的函数和头文件,例如对多线程编程的支持,以及一些新的数学函数等。 总之,C 11版本通过引入新的特性和改进,使得C语言在多线程编程和泛型编程方面更加强大和方便。这些改进使得C语言成为了一种更加现代和高效的编程语言。 ### 回答3: C语言的11个新特性有: 1. 波浪句法:在函数调用中可以使用波浪符号(~)来表示可变参数列表,简化了函数的定义和调用。 2. 布尔类型:引入了_Bool类型,可以表示真(true)和假(false)两个值,提供了更直观的布尔运算方式。 3. 引入了_Static_assert断言:可以在编译时进行静态断言检查,帮助发现一些显而易见的错误。 4. 对齐规范:引入了_Alignas和_Alignof关键字,用于指定变量的内存对齐方式,提高了数据访问的效率。 5. 内置预处理器:引入了_Static_assert断言和_Generic泛型选择器两个预处理器,提供了更强大的编译时能力。 6. 多字符常量:可以使用多个字符组成一个整数类型的常量,增加了对一些特殊字符的支持。 7. 增加了宽字符特性:引入了wchar_t类型和相关的宽字符函数,用于处理Unicode字符和多字节字符集。 8. 增加了数字常量的二进制表示法:可以使用0b或0B前缀来表示二进制数,方便程序员直接使用二进制数进行位运算。 9. 引入了包含括号的限定符:在声明语句中可以使用__attribute__((__packed__))和__attribute__((__aligned__))关键字来指定结构体或变量的对齐方式。 10. 增加了_Static_assert的能力:在静态断言中可以输出错误信息,帮助程序员更好地定位代码问题。 11. 增加了_Generic的能力:泛型选择器可以根据不同的类型选择对应的代码进行编译,提高了代码的灵活性和可重用性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值