学习c++的第三天

news/2024/7/24 9:50:10 标签: 学习, c++, 1024程序员节

目录

常量

字面常量

整数常量

浮点常量

布尔常量

字符常量

符号常量

const关键字

宏定义

修饰符类型

类型限定符

存储类

auto 存储类

register 存储类(被弃用)

static 存储类

extern 存储类

mutable 存储类

thread_local 存储类


常量

在C++中,常量是指在程序执行过程中其值不会发生改变的数据。常量可以分为字面常量和符号常量。

字面常量

字面常量是直接出现在程序中的具体数值,它们的值在编译时就已经确定。例如:

整数常量

在C++中,整数常量可以使用不同的进制表示:十进制、八进制和十六进制。

  • 十进制整数常量是最常见的,没有任何前缀。例如:85。
  • 八进制整数常量以0开头。例如:0213,表示的是十进制的139。
  • 十六进制整数常量以0x或0X开头。例如:0x4b,表示的是十进制的75。

整数常量还可以带有后缀来指定其类型,后缀可以是U和L的组合,大小写不敏感。其中,U表示无符号整数,L表示长整数。

例如:

  • 30u表示无符号整数常量。
  • 30l表示长整数常量。
  • 30ul表示无符号长整数常量。

需要注意的是,在编程中,如果整数常量超出了特定类型的表示范围,可能会导致溢出错误。因此,在选择整数常量时应该谨慎考虑。

浮点常量

在C++中,浮点常量是由整数部分、小数点、小数部分和指数部分组成。

浮点常量可以使用小数形式或指数形式来表示。

小数形式的浮点常量需要包含整数部分、小数点、小数部分,或者同时包含这两者。例如:3.14159。

指数形式的浮点常量需要包含小数点、指数,或者同时包含这两者,并且可以带有可选的符号。指数形式使用字母 'e' 或 'E' 引入指数部分。例如:314159E-5L,表示的是3.14159乘以10的负5次方。

需要注意的是,浮点常量必须是完整的,如果缺少整数部分、小数部分或指数部分,将会导致语法错误。

布尔常量

在C++中,布尔常量只有两个,分别是true和false,它们都是关键字。其中true代表真,false代表假。

布尔类型用于表示逻辑值,通常用于条件测试和控制流程。在条件测试中,如果布尔表达式的值为true,则执行相关代码,否则跳过。

需要注意的是,虽然true的值可以在某些情况下被解释为1,false的值可以被解释为0,但是我们不应该直接把它们看成数值。因为布尔类型只有真和假两种状态,没有其他的数值含义。

字符常量

字符常量是括在单引号中。如果常量以 L(仅当大写时)开头,则表示它是一个宽字符常量(例如 L'x'),此时它必须存储在 wchar_t 类型的变量中。否则,它就是一个窄字符常量(例如 'x'),此时它可以存储在 char 类型的简单变量中。

字符常量可以是一个普通的字符(例如 'x')、一个转义序列(例如 '\t'),或一个通用的字符(例如 '\u02C0')。

在 C++ 中,有一些特定的字符,当它们前面有反斜杠时,它们就具有特殊的含义,被用来表示如换行符(\n)或制表符(\t)等。下表列出了一些这样的转义序列码:

转义序列含义
\\\ 字符
\'' 字符
\"" 字符
\?? 字符
\a警报铃声
\b退格键
\f换页符
\n换行符
\r回车
\t水平制表符
\v垂直制表符
\ooo一到三位的八进制数
\xhh . . .一个或多个数字的十六进制数

以下是一些关于字符常量和转义序列的示例:

#include <iostream>

int main() {
    // 普通字符常量
    char c1 = 'A';
    std::cout << c1 << std::endl;  // 输出: A

    // 转义序列字符常量
    char c2 = '\t';  // 制表符
    std::cout << "Hello" << c2 << "World" << std::endl;  // 输出: Hello    World

    // 宽字符常量
    wchar_t wc = L'中';
    std::wcout << wc << std::endl;  // 输出: 中

    // 转义序列在字符串字面值中的应用
    std::cout << "This is a \"quoted\" string." << std::endl;  // 输出: This is a "quoted" string.
    std::cout << "This is a backslash: \\" << std::endl;  // 输出: This is a backslash: \

    return 0;
}

以上示例演示了如何使用普通字符常量、转义序列和宽字符常量。通过使用转义序列,我们可以插入特殊字符或实现格式化输出。同时,通过在字符串字面值中使用转义序列,我们可以插入引号或反斜杠等特殊字符。 

符号常量

符号常量:符号常量是通过标识符来表示的,其值在程序运行过程中不可修改。在C++中定义符号常量通常使用const关键字或者宏定义(#define)。例如:

const关键字

在C++中,可以使用const关键字来定义符号常量。const关键字用于声明一个不可修改的变量,即常量。它可以用于各种数据类型,包括基本数据类型、自定义类型以及指针类型。

下面是使用const关键字定义符号常量的示例:

#include <iostream>

int main() {
    const int MAX_VALUE = 100;
    const double PI = 3.14159;
    const std::string MESSAGE = "Hello, world!";

    std::cout << MAX_VALUE << std::endl;
    std::cout << PI << std::endl;
    std::cout << MESSAGE << std::endl;

    return 0;
}

在上述示例中,我们使用const关键字定义了三个不可修改的符号常量:MAX_VALUE是一个整数常量,PI是一个双精度浮点数常量,MESSAGE是一个字符串常量。注意,符号常量的名称通常使用全大写字母来表示,以增加其可读性。

宏定义

在C++中,宏定义是一种预处理指令,通过#define关键字来创建简单的替换文本。尽管宏定义可以用来定义符号常量,但它们没有类型检查,并且可能导致一些潜在的问题。因此,在C++中,推荐使用const关键字来定义符号常量,因为它提供了类型检查和更好的代码安全性。

以下是使用宏定义定义符号常量的示例:

#include <iostream>

#define MAX_VALUE 100
#define PI 3.14159
#define MESSAGE "Hello, world!"

int main() {
    std::cout << MAX_VALUE << std::endl;
    std::cout << PI << std::endl;
    std::cout << MESSAGE << std::endl;

    return 0;
}

在上述示例中,我们使用#define指令创建了三个宏定义,将它们分别替换为对应的值。然后,在主函数中使用这些宏定义。

需要注意的是,宏定义没有作用域的概念,它们是在预处理阶段进行简单的文本替换,可能会导致意外的副作用。因此,在C++中,推荐使用const关键字来定义符号常量。

使用常量的好处是可以提高代码的可读性和维护性,同时避免了不必要的错误。在程序中,常量可以像变量一样使用,但不能对其进行修改。

修饰符类型

C++ 允许在 char、int 和 double 数据类型前放置修饰符。

修饰符是用于改变变量类型的行为的关键字,它更能满足各种情境的需求。

下面列出了数据类型修饰符:

  • signed:表示变量可以存储负数。对于整型变量来说,signed 可以省略,因为整型变量默认为有符号类型。
  • unsigned:表示变量不能存储负数。对于整型变量来说,unsigned 可以将变量范围扩大一倍。
  • short:表示变量的范围比 int 更小。short int 可以缩写为 short。
  • long:表示变量的范围比 int 更大。long int 可以缩写为 long。
  • long long:表示变量的范围比 long 更大。C++11 中新增的数据类型修饰符。
  • float:表示单精度浮点数。
  • double:表示双精度浮点数。
  • bool:表示布尔类型,只有 true 和 false 两个值。
  • char:表示字符类型。
  • wchar_t:表示宽字符类型,可以存储 Unicode 字符。

修饰符 signed、unsigned、long 和 short 可应用于整型,signed 和 unsigned 可应用于字符型,long 可应用于双精度型。这些修饰符也可以组合使用,修饰符 signed 和 unsigned 也可以作为 long 或 short 修饰符的前缀。

例如:unsigned long int。C++ 允许使用速记符号来声明无符号短整数或无符号长整数。您可以不写 int,只写单词 unsigned、short 或 long,int 是隐含的。

下面是一些使用修饰符的示例:

#include <iostream>

int main() {
    signed int signedVariable = -10; // 可以存储负数
    unsigned int unsignedVariable = 15; // 不能存储负数
    short int shortVariable = 100; // 范围比 int 更小
    long int longVariable = 1000; // 范围比 int 更大
    long long int longLongVariable = 10000; // 范围比 long 更大
    float floatVariable = 3.14; // 单精度浮点数
    double doubleVariable = 3.1415; // 双精度浮点数
    bool boolVariable = true; // 布尔类型,只有 true 和 false
    char charVariable = 'A'; // 字符类型
    wchar_t wideCharVariable = L'中'; // 宽字符类型,可以存储 Unicode 字符

    std::cout << "signedVariable: " << signedVariable << std::endl;
    std::cout << "unsignedVariable: " << unsignedVariable << std::endl;
    std::cout << "shortVariable: " << shortVariable << std::endl;
    std::cout << "longVariable: " << longVariable << std::endl;
    std::cout << "longLongVariable: " << longLongVariable << std::endl;
    std::cout << "floatVariable: " << floatVariable << std::endl;
    std::cout << "doubleVariable: " << doubleVariable << std::endl;
    std::cout << "boolVariable: " << boolVariable << std::endl;
    std::cout << "charVariable: " << charVariable << std::endl;
    std::wcout << "wideCharVariable: " << wideCharVariable << std::endl; // 注意使用 std::wcout 输出宽字符

    return 0;
}

在上述示例中,我们演示了如何使用不同的修饰符类型来声明不同的变量。signed 和 unsigned 可以应用于整型,long 可以应用于双精度型。修饰符也可以组合使用,例如 unsigned long int。

类型限定符

C++中的类型限定符用于在定义变量或函数时改变它们的默认行为。下面是常见的C++类型限定符:

1、const:const关键字用于定义常量,表示该变量的值不能被修改。例如:

const int NUM = 10; // 定义常量NUM,其值不可修改
const int* ptr = &NUM; // 定义指向常量的指针,指针所指的值不可修改

2、volatile:volatile修饰符告诉编译器该变量的值可能会被程序以外的因素改变,如硬件或其他线程。这可以防止编译器进行某些优化,以确保每次访问该变量时都从内存中获取最新的值。例如:

volatile int num = 20; // 定义变量num,其值可能会在未知的时间被改变

3、restrict:restrict修饰的指针是唯一一种访问它所指向的对象的方式。该限定符只在C99标准中引入。它用于告诉编译器,通过这个指针对指向的内存进行的读写操作不会与其他指针冲突,从而进行更多的优化。例如:

int* restrict ptr = nullptr; // 声明一个restrict修饰的指针

4、mutable:mutable关键字用于类中的成员变量,表示这些成员变量可以在const成员函数中被修改。默认情况下,const成员函数不能修改对象的数据成员,但使用mutable关键字可以例外。例如:

class Example {
public:
    int get_value() const {
        return value_; // const成员函数不会修改对象中的数据成员
    }
    void set_value(int value) const {
        value_ = value; // mutable关键字允许在const成员函数中修改成员变量
    }
private:
    mutable int value_;
};

5、static:static关键字用于定义静态变量,表示该变量的作用域仅限于当前文件或当前函数内,不会被其他文件或函数访问。静态变量在程序运行期间只有一份实例化的副本,即使函数多次调用,其值也会保持。例如:

void example_function() {
    static int count = 0; // static关键字使变量count存储在程序生命周期内都存在
    count++;
}

6、register:register关键字用于定义寄存器变量,表示该变量被频繁使用,可以存储在CPU的寄存器中,以提高程序的运行效率。然而,现代编译器通常会自动优化寄存器变量,因此使用register关键字并不一定能够强制将变量存储在寄存器中。例如:

void example_function(register int num) {
    // register关键字建议编译器将变量num存储在寄存器中
    // 以提高程序执行速度
    // 实际上是否会存储在寄存器中由编译器决定
}

这些类型限定符提供了额外的语义信息,可以帮助开发人员更好地控制变量的行为和性能。

存储类

auto 存储类

根据C++标准,auto关键字在C++11及以后的版本中有两种用法。

1、类型推断:在变量声明时,根据初始化表达式自动推断变量的类型。例如:

auto f = 3.14; // 推断为double类型
auto s = "hello"; // 推断为const char*类型
auto z = new auto(9); // 推断为int*类型

2、函数返回类型占位符:在函数声明时,用auto作为函数返回类型的占位符,编译器会根据函数体中的返回语句推断函数的返回类型。例如:

auto add(int a, int b) -> int {
    return a + b;
}

需要注意的是,C++11之前的标准并不支持函数返回类型占位符的使用,而是使用了其他方式来实现相似的功能,例如使用模板或尾返回类型声明。

总结起来,C++11以后的auto关键字用于类型推断和函数返回类型占位符两种情况。类型推断用于变量声明时根据初始化表达式自动推断变量的类型,函数返回类型占位符用于函数声明时作为返回类型的占位符,由编译器根据函数体中的返回语句推断实际的返回类型。

注意:C++98标准中auto关键字用于自动变量的声明,但由于使用极少且多余,在 C++17 中已删除这一用法。

register 存储类(被弃用)

register是一个C++中的存储类说明符,它被用来建议编译器将变量存储在寄存器中以获得更快的访问速度。然而,现代编译器通常已经足够聪明,能够自动将需要频繁访问的变量存储到寄存器中,因此使用register关键字的效果可能不会像预期的那样有效。

register关键字的语法如下:

register int count;

在这里,count变量被建议存储在寄存器中。但是,编译器可以自行决定是否实际将该变量存储在寄存器中,因为在某些情况下,寄存器可能已被分配用于其他目的。因此,使用register关键字不一定能够获得性能上的提升。

需要注意的是,从C++17开始,register 关键字被弃用。编译器不再需要严格遵循register关键字的建议,它可能会忽略该关键字并将变量存储在内存中而不是寄存器中。这意味着register关键字的使用已经不再具有实际意义,并且在现代C++代码中应该避免使用。

当然,编译器仍然可以选择将变量存储在寄存器中,但它不再受到register关键字的强制要求。由于现代编译器通常能够智能地进行寄存器分配优化,因此人工使用register关键字的效果往往不如预期。

static 存储类

static 存储类可以用于修饰局部变量、全局变量和类成员变量,具体含义如下:

  • 修饰局部变量:在程序的生命周期内保持局部变量的存在,不需要在每次进入和离开作用域时进行创建和销毁。
  • 修饰全局变量:限制变量的作用域在声明它的文件内,只能被该文件内的函数访问。
  • 修饰类成员变量:导致仅有一个该成员的副本被类的所有对象共享。

注意:

  • 在函数内部,static 变量只在第一次调用时初始化,之后保留上一次的值。
  • 在全局作用域中,static 变量只能被声明一次,在其他文件中不能访问。
  • 在类中,static 成员变量和函数属于整个类而不是某个对象,可以通过类名访问。
#include <iostream>

void foo() {
    static int count = 0; // 静态局部变量,保持其值在函数调用之间
    count++;
    std::cout << "Count: " << count << std::endl;
}

static int x = 10; // 静态全局变量,只能在该文件内访问

void printX() {
    std::cout << "x: " << x << std::endl;
}

int main() {
    foo(); // 输出:Count: 1
    foo(); // 输出:Count: 2
    foo(); // 输出:Count: 3

    printX(); // 输出:x: 10

    return 0;
}

extern 存储类

extern 用于在一个文件中声明一个全局变量或函数,以便在其他文件中引用它们。

当我们有多个文件共享相同的全局变量或函数时,可以在其中一个文件中进行定义,并在其他文件中使用 extern 来声明该变量或函数的存在。这样,在编译和链接过程中,编译器就会知道该变量或函数的定义在其他文件中。

这里是一个示例:

在 file1.cpp 文件中定义一个全局变量:

// file1.cpp
int globalVariable = 10;

在 file2.cpp 文件中使用 extern 来引用 globalVariable 变量:

// file2.cpp
extern int globalVariable;

int main() {
    // 使用 globalVariable
    // ...
    return 0;
}

通过使用 extern 关键字,我们可以在 file2.cpp 中引用 globalVariable 变量,而不需要重新定义它。

同样,我们也可以使用 extern 来引用在其他文件中定义的全局函数。

mutable 存储类

mutable 是 C++ 中的一个存储类,用于声明一个非常量的类成员,即使该成员在一个 const 对象中,也可以被修改。通常情况下,const 对象的数据成员是不能被修改的,但使用 mutable 关键字可以使某些数据成员例外。

这个关键字通常用于需要缓存的变量,它们的值可以在 const 对象中被修改,而无需改变 const 对象的可靠性。mutable 成员变量不受 const 限制,可以在 const 对象上被修改,因此被称为“可变的 const 成员”。

当我们将一个成员变量声明为 mutable 时,即使该成员变量在 const 对象中,也可以被修改。下面是一个使用 mutable 的代码示例,展示了如何在 const 成员函数中修改 mutable 成员变量:

#include <iostream>

class Example {
public:
    Example(int value) : data(value) {}

    void printData() const {
        std::cout << "Data: " << data << std::endl;
        incrementData();
        std::cout << "Modified Data: " << data << std::endl;
    }

private:
    mutable int data;

    void incrementData() const {
        ++data;
    }
};

int main() {
    const Example obj(10);
    obj.printData();

    return 0;
}

在这个示例中,我们定义了一个名为 Example 的类,它有一个私有的 mutable 成员变量 data。在 printData() 函数中,我们首先输出 data 的值,然后调用 incrementData() 函数来对 data 进行递增操作,并再次输出 data 的值。

在主函数中,我们创建了一个 const 的 Example 对象 obj,并调用其 printData() 函数。由于 printData() 函数是 const 成员函数,并且 data 被声明为 mutable,所以我们可以在 const 对象中修改它。运行上述代码,输出将会是:

Data: 10
Modified Data: 11

从输出结果可以看出,尽管对象 obj 是 const 对象,但在 const 成员函数中我们仍然可以修改 mutable 成员变量 data 的值。

这个示例展示了 mutable 存储类的用法,它使某些成员变量可以在 const 对象中被修改。但是请注意,虽然 mutable 可以使某些数据成员例外,但应该谨慎使用它,因为它可能会使 const 对象的状态变得不可预测,从而导致一些问题。因此,建议只在确实需要缓存数据的情况下才使用 mutable。

thread_local 存储类

thread_local 是 C++11 引入的一个存储类说明符,用于声明线程局部存储(Thread Local Storage, TLS)。它告诉编译器每个线程都有其自己的变量实例,每个线程对该变量的修改不会影响其他线程的实例。

使用 thread_local 关键字可以在多线程程序中创建线程本地变量,这些变量的生命周期与线程的生命周期相同,并且每个线程都有自己的变量副本。这样,每个线程都可以独立地操作自己的变量副本,而不会互相干扰。

thread_local 说明符可以与 static 或 extern 合并,用于静态或外部变量的声明和定义。例如:

thread_local static int count = 0;

这将创建一个静态的 thread_local 变量 count,并将其初始化为 0。

请注意,thread_local 不能用于函数声明或定义,它只能用于数据声明和定义。

下面是一个使用 thread_local 的示例代码:

#include <iostream>
#include <thread>

thread_local int threadId;  // 声明一个 thread_local 变量

void printThreadId() {
    std::cout << "Thread ID: " << threadId << std::endl;
}

int main() {
    std::thread t1([]() {
        threadId = 1;  // 每个线程都有自己的 threadId 变量副本
        printThreadId();
    });

    std::thread t2([]() {
        threadId = 2;
        printThreadId();
    });

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

    return 0;
}

在上述代码中,我们首先声明了一个 thread_local 整数变量 threadId。然后,我们创建了两个线程 t1 和 t2,每个线程都分别给 threadId 赋值,并调用 printThreadId() 函数打印线程的 ID。

运行这个程序,输出结果(随机)可能类似于:

Thread ID: 1
Thread ID: 2

从输出结果可以看出,每个线程都有自己的 threadId 变量副本,并且修改其中一个线程的变量不会对其他线程产生影响。

thread_local 的作用是为每个线程创建独立的变量副本,在多线程编程中非常有用。它可以避免线程间的竞争条件和数据共享问题,并提供了一种简单而有效的方式来处理线程特定的数据。


http://www.niftyadmin.cn/n/5144218.html

相关文章

C++ 类的设计

一、c类的设计 类 是一种将抽象转换为用户定义类型的 C工具&#xff0c; 它将数据表示操纵数据的方法组合成一个整洁的包。 语法&#xff1a; 其中"class类名"称为类头。花括号中的部分称为类体&#xff0c;类体中定义了类成员表。 在C中&#xff0c;类是一种数据类型…

RDKit(2023.09.1 )环系统模板

环境 Python 3.9RDKit 2023.09.1from rdkit import Chem from rdkit.Chem import rdDepictor from rdkit.Chem import Draw from rdkit.Chem.Draw import IPythonConsoleIPythonConsole.ipython_useSVG=True %matplotlib inline import rdkit print(rdkit.__version__) 2023.09…

并查集易错点

并查集就俩核心点,1是找父节点,2是合并 1: return fa[x] x ? x : fa[x] find(fa[x]); 2. fa[find(a)] find(b) 第二步还挺容易写错的,左边是find(a)的根,而不是fa[a]

OpenSearch使用scroll滚动搜索实战

文章目录 前言当前环境实战遇到的问题 前言 起源和开源性质: Elasticsearch&#xff1a;Elasticsearch最初由Elastic公司开发&#xff0c;后来开源&#xff0c;但采用了Elastic License&#xff0c;这是一种有限制的许可协议。 OpenSearch&#xff1a;OpenSearch是从Elasticsea…

2021~2023年度长垣起重机博览会最佳产品彩页(修订中)

1.河南恒达 比较完善的起重量限制器产品线分类&#xff0c;提供了监控参数一览表。 2.沪源电机 详细的电机参数&#xff0c;这基本上可以作为电机发展的历史资料来搜集。 3.英威腾 详细的变频器功能 4.杭州浙起 详尽的电动葫芦结构展示&#xff0c;电动葫芦参数展示 5.…

Linux学习第30天:Linux 自带的 LED 灯驱动实验:驱动开发思维方式的转变势在必行

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 学习嵌入式Linux驱动开发整整30天了。今天简单做一个小结。因为之前的主要工作是做ARM的裸机开发&#xff0c;所以接触Linux以后感觉很多东西都变了。不仅仅包括…

【数据结构二叉树】先序层序建立、递归非递归遍历层序遍历、树高、镜面、对称、子树、合并、目标路径、带权路径和等等

二叉树 文章目录 二叉树1. 二叉树的建立&#xff08;递归创建&#xff0c;结构体指针形式&#xff09;1.1. 先序建立1.2. 层序建立 2. 递归遍历(结构体指针)2.1. 先序遍历2.2. 中序遍历2.3. 后序遍历 3. 非递归遍历(结构体指针)3.1. 层次遍历3.2. 后序遍历(非递归) 4. 求树的高…

C语言队列实现

1.知识百科 队列是一种特殊的线性表&#xff0c;特殊之处在于它只允许在表的前端&#xff08;front&#xff09;进行删除操作&#xff0c;而在表的后端&#xff08;rear&#xff09;进行插入操作&#xff0c;和栈一样&#xff0c;队列是一种操作受限制的线性表。进行插入操作的…