C11

本文深入探讨C++11引入的重要新特性,包括pair类型简化声明、const和constexpr变量、void*指针的使用、继承关系改进、类型推导(auto和decltype)、尾返回类型、nullptr和constexpr函数。通过实例解释这些特性的应用和优势,帮助读者掌握C++11带来的现代化编程体验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    pair是一个模板数据类型,其中包含两个数据值,两个数据值可以不同

如 pair<int,string>a(2,"fgh");则a是一个pair类型,它包括两个数据,第一个数据是int型2,第二个数据是string型"fgh"。

   由于pair类型的使用比较繁琐,因为如果要定义多个形同的pair类型的时候,可以时候typedef简化声明:

typedef pair<string, string> author;

author pro("May", "Lily");

author joye("James", "Joyce");

对pair对象的操作

  • 对于pair类,由于它只有两个元素,分别名为first和second,因此直接使用普通的点操作符即可访问其成员

     pair<string, string> a("Lily", "Poly"); 

     string name;

     name = pair.second;

在使用map的插入功能时,可以这样来写:

        map<string,int> m;

        m.insert(pair<string,int>("Jake",3));

     常量表达式(const expression):是指值不会改变并且在编译过程中就得到计算结果的表达式。(运行得到结果的不能成为常量表达式)。

             const int i=3;    //是一个常量表达式

             const int j=i+1; //是一个常量表达式

             int k=23;        //k的值可以改变,从而不是一个常量表达式

             const int m=f(); //不是常量表达式,m的值只有在运行时才会获取。

    constexpr变量

        C++11允许声明constexpr类型来由编译器检验变量的值是否是一个常量表达式。声明为constexpr的必须是一个常量,并且只能用常量或者常量表达式来初始化

             constexpr int i=3;

             constexpr int j=i+1;

             constexpr int k=f(); //只有f()是一个constexpr函数时k才是一个常量表达式

一般来说,若果一旦认定变量是一个常量表达式,那就把它声明为constexpr类型

尽管指针和引用都可以定义为constexpr,但是他们的初始值却受到了严格的限制。一个constexpr指针的初始值必须是nullptr或者0,或者是存储某个固定地址的对象。函数体中定义的变量并非存放在固定地址中,因此constexpr指针不可以指向这样的变量。相反的,对于所有函数体之外的对象地址是固定不变的,可以用constexpr来定义;

必须明确一点,在constexpr声明中,如果定义了一个指针,限定符号constexpr仅仅对指针有效,与指针所指对象无关。

    const int *p=nullptr;  //p是一个指向整型常量的指针(pointer to const)

    constexpr int *p1=nullptr; //p1是一个常量指针(const pointer)

void*类型的指针

  void*是一种特殊的指针类型,可以用来存放任意对象的地址。一个void*指针存放着一个地址,这一点和其他指针类似。不同的是,我们对它到底储存的是什么对象的地址并不了解;

   比如:double a=2.3;

            int b=5;

            void *p=&a;

            cout<<p<<endl;   //输出了a的地址

            p=&b;

            cout<<p<<endl;   //输出了b的地址

             //cout<<*p<<endl;这一行不可以执行,void*指针只可以储存变量地址,不冷直接操作它指向的对象

利用void*可以直接做的事比较有限:拿他和别的指针比较,作为函数的输入或者输出,或者赋值给另外一个void*的指针。不可以操作void*指向的对象。如此一来,内存空间就仅仅是内存空间,没办法访问内存空间指向的对象。

继承关系·

     C++继承方式有公有继承,私有继承,保护继承。值得注意的是,基类私有成员在任用任何继承方式下都是隔离的,也就是视派生类为外人。在公有继承中,基类的每个成员在子类中保证相同的访问方式,在基类为public 成员,则在子类也是public成员,在基类为保护成员则在子类也是保护成员。对于保护继承,基类的公有成员和保护成员继承到子类都变成了保护成员。

   C++具有单一继承和多重继承。多重继承在实现时并不容易,主要是编译问题,模糊性问题,调试问题也很多,一般只有高级程序员才使用多重继承。如下例子:

#include<iostream>
using namespace std;

class Furniture{
protected :
double weight;
public:
void setWeight(double i){ weight = i; }
double getWeight(){ return weight; }
};

class Bed :virtual public Furniture{
public:
void sleep();
};

class Sofa :virtual public Furniture{
public:
void wacthTV(){};
};

class SleeperSofa :public Sofa, public Bed{
public:
void floadOut(){};
};

int main(){
SleeperSofa f;
f.setWeight(1.0);
}

上面程序中,Furniture是最初的基类,Bed类和Sofa类都从Furniture虚拟继承而来,而SleepSofa则继承于Bed和Sofa类,若不加上virtual关键字,则会产生歧义,这是由于在子类SleepSofa中继承的weight和setWeight到低来自哪里?是继承自Bed类还是,Sofa类,这样就产生了模糊不清的问题,如今的SleepSofa继承了Furniture的两个副本。当加上virtual关键字后,表明,Bed类和Sofa类都从Furniture虚拟继承,这样后代就保证了只继承一个副本,就不会产生歧义。

类推类型与区间迭代

在传统 C 和 C++中,参数的类型都必须明确定义,当我们面对一大堆复杂的模板类型时,必须明确的指出变量的类型才能进行后续的编码,这样很不方便,而向python等语言就要显得智能得多。C++11 引入了 autodecltype 这两个关键字实现了类型推导,让编译器来操心变量的类型。这使得 C++ 也具有了和其他现代编程语言一样,某种意义上提供了无需操心变量类型的使用习惯。

auto关键字

  使用 auto 关键字是一个存储类型说明符,C++11将用于进行类型推导。这个要求进行显示初始化,

auto m = 120; // m是int类型
auto p = &m; // p是int类型指针

以前遍历一个迭代器需要这样做

vector<int>::iterator it;
for(it = vec.begin(); it != vec.end(); it++){
    ;
}

有了auto后,遍历方便很多,只需要

for(auto x : vec){
        ;
}

注意:auto 不能用于函数传参,这将无法通过编译的(考虑重载的问题,我们应该使用模板)。

decltype 关键字

  关键字decltype 将变量的类型声明为表达式指定的类型。下面语句的意思是让y的类型与x相同:

int x = 5;
decltype(x) y;

再看下面例子

#include<bits/stdc++.h>
using namespace std;

int main(){
    double x;
    decltype(&x) y; // 定义y为double类型指针   
    y = new double{4.0};
    cout << *y << endl;
    return 0;
}

在定义类模板的时候特别有用,因为只有等到模板实例化后才知道具体类型

#include<bits/stdc++.h>
using namespace std;

template<typename T, typename U>
void func(T a, U b){
    decltype (a*b) x;
}

int main(){
    func(2, 2.5); // 此时x是double类型
    func(2u, 6); // 此时x是unsinged int 类型

    return 0;
}

特别注意,decltype指定类型可以是引用或者const,如下例子一样:

#include<bits/stdc++.h>
using namespace std;

int main(){
    int a = 1;
    int &ra = a;
    const int b = 3;
    decltype(b) y = 4;  // y的类型为const int
    int c = 0;
    decltype(ra) x = c; // x的类型是int &
    return 0;
}

尾返回类型

C++11 增加了一种新语法,在函数名和函数列表后面指定返回类型

double f1(double, int);  // 传统语法
auto f2(double, int) -> double;     // 新语法 ,返回double 类型

这种设计将给C++带来很多方便,例如定义一个模板类返回2个数的和,传统方法需要写为:

template<class T, class U, class R>
R add(T a, U b){
    return a + b;
}

由于不知道返回类型,这样传递返回类型是一种很丑陋的写法,有了后置返回类型,上述代码可以写为:

template<class T, class U>
auto add(T a, U b) -> decltype(a + b){
    return a + b;
}

C++1x (本教程中指 C++11/14, 甚至 C++17) 为传统 C++ 注入的大量特性使得整个 C++ 变得更加像一门现代化的语言。C++1x 不仅仅增强了 C++ 语言自身的可用性,auto 关键字语义的修改使得我们更加有信心来操控极度复杂的模板类型。同时还对语言运行期进行了大量的强化,Lambda 表达式的出现让 C++ 具有了『匿名函数』的『闭包』特性,而这一特性几乎在现代的编程语言(诸如 Python/Swift/... )中已经司空见惯,右值引用的出现解决了 C++ 长期以来被人诟病的临时对象效率问题等等。 C++1x 为自身的标准库增加了非常多的工具和方法,诸如在语言层面上提供了 std::thread 支持了并发编程,在不同平台上不再依赖于系统底层的 API,实现了语言层面的跨平台支持;std::regex提供了完整的正则表达式支持等等。

被弃用的特性

弃用不等于废弃,只是用于暗示程序员这些特性将从未来的标准中消失,应该尽量避免使用。但是,已弃用的特性依然是标准库的一部分,并且出于兼容性的考虑,这些特性其实会『永久』保留。

  • 如果一个类有析构函数,为其生成拷贝构造函数和拷贝赋值运算符的特性被弃用了。
  • 不再允许字符串字面值常量赋值给一个 char *。如果需要用字符串字面值常量赋值和初始化一个char*,应该使用 const char * 或者 auto
char \*str = "hello world!"; // C++11中无法通过编译
const char *str = "hello world!";  // C++11使用
  • C++98 异常说明、 unexcepted_handler、set_unexpected() 等相关特性被弃用,应该使用 noexcept。auto_ptr 被弃用,应使用 unique_ptr。
  • register 关键字被弃用。
  • bool 类型的 ++ 操作被弃用。
  • C 语言风格的类型转换被弃用,应该使用 static_cast、reinterpret_cast、const_cast 来进行类型转换。

分类: C++新标准(C++11)

nullptr

  nullptr出现的目的自然是替换NULL的低位。C++可能会将NULL、0视为同一种东西。这取决于编译器是如何定义的,有的编译器定义NULL为 ( (void * )0) ,有的直接定义为0,这样的化在程序中可能会出现意想不到的错误,例如它会破坏函数的重载功能,考虑下面的重载函数

void function(char *p);
void function(int x);

那么当调用 function(0)时将会调用哪一个函数呢?这可能会在不同编译器产生不同结果。C++11引入nullptr 专门来区分0和NULL,nullptr的类型是nullptr_t

#include<iostream>
using namespace std;

void test(char*p){
    puts("调用的是char *");
}

void test(int x){
    puts("调用的是int");
}

int main(){
    test(0);
    test(nullptr);
    return 0;
}
/*
调用的是int
调用的是char *
*/

今后尽量使用nullptr避免使用NULL

constexpr

  C++本身具备常量表达式的概念,常量表达式(const expression)是指值不会改变并且在编译过程中就得到计算结果的表达式,如果编译器能够在编译时就把这些表达式直接优化并植入到程序运行时,将能增加程序的性能。C++11 提供了 constexpr 让用户显式的声明函数或对象构造函数在编译器会成为常数,在C++11中常量constexpr修饰的函数可以采用递归形式。

<think>好的,用户现在问的是关于C11的问题。我需要先回顾之前的对话历史,看看有没有相关的上下文。之前用户询问的是如何在Windows下的VS2022中使用Sleep函数,我详细介绍了头文件、函数使用、常见问题以及跨平台兼容性等内容。现在用户的问题转向了C11标准,这是一个关于C语言版本的问题。 首先,我需要确定用户的具体需求。他们可能想知道C11的新特性,或者如何在VS2022中启用C11支持,或者可能遇到了兼容性问题。由于用户之前在使用VS2022,可能需要结合这个环境来解释C11的支持情况。 接下来,我需要整理C11的主要特性,比如多线程支持、泛型宏、匿名结构体/联合、对齐处理、静态断言等。同时,要指出VS2022对C11的支持程度,因为微软的编译器对C标准的支持历来不如GCC或Clang全面,特别是C11的某些可选特性,如stdatomic.h或threads.h可能不被支持。 然后,用户可能需要知道如何在VS2022中配置项目以使用C11。需要检查VS的编译器选项,但根据我所知,MSVC默认使用C++的编译器,对于C语言的支持可能有限,可能需要使用其他编译器如Clang或调整项目设置。同时,要提醒用户注意VS对C11的支持可能存在部分缺失,建议使用较新的编译器如GCC或Clang来获得完整支持。 此外,用户可能关心如何验证他们的代码是否符合C11标准,可以提到使用预定义宏如__STDC_VERSION__来检查标准版本。还要给出一个简单的代码示例,展示如何检查C11支持,并说明在不同编译器下的编译命令。 需要注意避免过于技术性的术语,保持解释清晰易懂。同时,要指出VS的局限性,并提供替代方案,比如使用跨平台工具链,或者切换到其他IDE如Visual Studio Code配合其他编译器。 最后,确保回答结构清晰,分点列出主要特性、VS2022的支持情况、配置步骤、代码示例以及兼容性建议,帮助用户全面理解C11及其在VS中的使用情况。</think>### C11 标准详解(ISO/IEC 9899:2011) --- #### **1. 核心特性** C11 是继 C99 之后的重要更新,主要改进: - **多线程支持** 新增 `<threads.h>` 头文件,提供线程创建/同步接口: ```c #include <threads.h> thrd_t tid; int task(void *arg) { /* 线程任务 */ return 0; } thrd_create(&tid, task, NULL); // 创建线程 ``` - **泛型选择** 通过 `_Generic` 实现类型敏感的宏: ```c #define print_type(X) _Generic((X), \ int: "int", double: "double", default: "unknown") printf("%s", print_type(3.14)); // 输出 "double" ``` - **匿名结构体/联合** 简化嵌套结构定义: ```c struct SensorData { int id; union { // 匿名联合 float temp; char status[4]; }; }; ``` - **对齐处理** `<stdalign.h>` 提供对齐控制: ```c alignas(16) int buffer[4]; // 16字节对齐 printf("%zu", alignof(buffer)); // 输出 16 ``` - **静态断言** 编译期断言检查: ```c static_assert(sizeof(int) == 4, "int必须为4字节"); ``` --- #### **2. Visual Studio 2022 对 C11 的支持** - **编译器限制** MSVC 对 C11 支持不完整(截至2023年): - ✅ 支持:`_Generic`、匿名结构体、静态断言 - ❌ 不支持:`<threads.h>`、`<stdatomic.h>` - **配置步骤** 1. 新建项目时选择 **C++ 控制台应用** 2. 文件后缀改为 `.c`(强制使用C编译器) 3. 右键项目 → 属性 → **C/C++ → 语言 → C 语言标准** → 选 `ISO C11` --- #### **3. 代码验证示例** ```c #include <stdio.h> #include <stdalign.h> int main() { // 检查C标准版本 #if defined(__STDC_VERSION__) printf("C标准版本: %ld\n", __STDC_VERSION__); #endif // 泛型宏演示 #define type_name(X) _Generic((X), \ int: "int", double: "double", default: "unknown") printf("类型: %s\n", type_name(3.14)); // 静态断言 static_assert(CHAR_BIT == 8, "要求8位字节"); return 0; } ``` **编译命令(非VS环境)**: ```bash gcc -std=c11 test.c -o test # GCC clang -std=c11 test.c -o test # Clang ``` --- #### **4. 兼容性建议** - **跨平台开发**: - 使用 **`<stdatomic.h>`** 替代编译器特定原子操作 - 通过 **条件编译** 处理平台差异: ```c #ifdef _WIN32 #include <windows.h> void sleep_ms(int ms) { Sleep(ms); } #else #include <unistd.h> void sleep_ms(int ms) { usleep(ms * 1000); } #endif ``` - **替代工具链**: 若需完整C11支持,建议使用: - **WSL + GCC/Clang**(Windows子系统Linux) - **Visual Studio Code + MinGW-w64** --- #### **5. 新标准对比** | 特性 | C99 | C11 | |--------------------|---------------|-------------------| | 多线程 | 无 | `<threads.h>` | | 泛型编程 | 无 | `_Generic` | | 匿名结构体 | 不支持 | 支持 | | 对齐控制 | 编译器扩展 | `<stdalign.h>` | | 安全函数 | 部分 | 新增`_s`后缀函数 | 建议持续关注 **C17(C18)** 和未来 **C2x** 标准的演进。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值