C++ 入门(八)— 常量和字符串

常量和字符串

  • 常量变量
  • 常量表达式
    • 编译时优化
  • Constexpr 变量
  • std::string
    • 字符串输出 std::cout
    • std::string可以处理不同长度的字符串
    • 字符串输入 std::cin
    • 用于输入文本std::getline()
    • 不要按值传递
    • Constexpr 字符串
  • std::string_view
    • 可以使用许多不同类型的字符串进行初始化
    • 可以接受许多不同类型的字符串参数
    • std::string_view不会隐式转换为std::string
    • constexpr std::string_view
  • std::string_view std::string 区别
  • std::string_view 和 std::string 哪个更适合用于函数参数传递

常量变量

在编程中,常量是在程序执行期间不得更改的值。

在C++ 中,命名常量是与标识符关联的常量值。这些有时也称为符号常量,有时也称为常量。

在 C++ 中定义命名常量有三种方法:

  • 常量变量。
  • 带有替换文本的类似对象的宏。
  • 枚举常量。

常量变量

值不能改变的变量称为常量变量。

声明 const 变量:

const double gravity { 9.8 };  //首选在类型之前使用const
int const sidesInSquare { 4 }; // “east const”风格,可以,但不是首选

必须初始化常量变量

在定义常量变量时,必须对其进行初始化,而且无法通过赋值更改该值。

int main()
{
    const double gravity; //error:const变量必须初始化
    gravity = 9.9;        // error:const变量不能更改

    return 0;
}

但是,常量变量可以从其他变量(包括非常量变量)初始化:

int main()
{
    int age{};
    const int constAge { age }; // 使用非const值初始化const变量
    age = 5;      // age是非const的,所以我们可以改变它的值
    constAge = 6; // error: constAge是const,所以我们不能改变它的值

    return 0;
}

常量函数参数

函数参数可以通过以下关键字成为常量:

#include <iostream>

void printInt(const int x)
{
    std::cout << x << '\n';
}

int main()
{
    printInt(5); // 5 will be used as the initializer for x
    printInt(6); // 6 will be used as the initializer for x

    return 0;
}

这里,函数调用中参数的值将用作 的初始值设定项。

注意:按值传递时不要使用,因为值传递 const 对象通常没有多大意义,因为它们是无论如何都会被销毁的临时副本。

常量返回值

函数的返回值也可以设为 const:

const int getValue()
{
    return 5;
}

注意:按值返回时不要使用,因为按值返回 const 对象通常没有多大意义,因为它们是无论如何都会被销毁的临时副本。

首选常量变量而不是预处理器宏

有三个主要问题:

  • 最大的问题是宏不遵循正常的 C++ 范围规则。#defined 宏后,当前文件中所有后续出现的宏名称都将被替换。
    #include <iostream>
    
    void someFcn()
    {
    #define gravity 9.8
    }
    
    void printGravity(double gravity) // 包括这个,导致编译错误
    {
      std::cout << "gravity: " << gravity << '\n';
    }
    
    int main()
    {
      printGravity(3.71);
    
        return 0;
    }
    
    在这里插入图片描述
  • 其次,使用宏调试代码通常更难。尽管源代码将具有宏的名称,但编译器和调试器永远不会看到宏,因为它在运行之前已被替换。
  • 第三,宏替换的行为与 C++ 中的其他所有内容不同。因此,很容易犯不经意的错误。

常量表达式

一种始终可以在编译时计算的表达式称为“常量表达式”。

常量表达式的精确定义很复杂,因此我们将采用简化的观点:常量表达式是仅包含编译时常量和支持编译时计算的运算符/函数的表达式。

编译时常量是一个常量,其值必须在编译时已知。这包括:

  • 文字(例如“5”、“1.2”)。
  • Constexpr 变量。
  • 具有常量表达式初始值设定项的常量积分变量(例如 )。在现代C++中,constexpr变量是首选。
  • 非类型模板参数。
  • 枚举器。

不是编译时常量的常量变量有时称为运行时常量。运行时常量不能在常量表达式中使用。

编译时优化

表达式的编译时求值

比如:const int x { 3 + 4 }; 改成 const int x { 7 };

常量变量更易于优化

因为现在是 const,所以编译器现在有一个保证,在初始化后无法更改。这使得编译器更容易理解它可以安全地从这个程序中进行优化。

根据编译器能够优化变量的可能性对变量进行排名:

  • 编译时常量变量(始终符合优化条件)
  • 运行时常量变量
  • 非常量变量(可能仅在简单情况下进行优化)

Constexpr 变量

constexpr(“常量表达式”的缩写)变量必须使用常量表达式进行初始化,否则将导致编译错误。

#include <iostream>

int five()
{
    return 5;
}

int main()
{
    constexpr double gravity { 9.8 }; // ok: 9.8 is a constant expression
    constexpr int sum { 4 + 5 };      // ok: 4 + 5 is a constant expression
    constexpr int something { sum };  // ok: sum is a constant expression

    std::cout << "Enter your age: ";
    int age{};
    std::cin >> age;

    constexpr int myAge { age };      // compile error: age is not a constant expression
    constexpr int f { five() };       // compile error: return value of five() is not a constant expression

    return 0;
}

常量与 constexpr

  • 对于变量,const 表示对象的值在初始化后无法更改。Constexpr 表示对象必须具有在编译时已知的值。
  • Constexpr 变量是隐式 const。常量变量不是隐式的 constexpr(具有常量表达式初始值设定项的常量整数变量除外)。

Constexpr 函数可以在编译时计算

constexpr 函数是一个函数,其返回值可以在编译时计算。要使函数成为 constexpr 函数,我们只需在返回类型前面使用关键字即可。

#include <iostream>

constexpr int greater(int x, int y) // now a constexpr function
{
    return (x > y ? x : y);
}

int main()
{
    constexpr int x{ 5 };
    constexpr int y{ 6 };

    // We'll explain why we use variable g here later in the lesson
    constexpr int g { greater(x, y) }; // will be evaluated at compile-time

    std::cout << g << " is greater!\n";

    return 0;
}

函数调用将在编译时而不是运行时进行评估。

在编译时计算函数调用时,编译器将计算函数调用的返回值,然后将函数调用替换为返回值。

所以在我们的例子中,调用 将被函数调用的结果替换,即整数值。换句话说,编译器将编译以下内容:

#include <iostream>

int main()
{
    constexpr int x{ 5 };
    constexpr int y{ 6 };

    constexpr int g { 6 }; // greater(x, y) evaluated and replaced with return value 6

    std::cout << g << " is greater!\n";

    return 0;
}

若要符合编译时计算的条件,函数必须具有 constexpr 返回类型,并且在编译时计算时不调用任何非 constexpr 函数。此外,对函数的调用必须具有常量表达式的参数(例如编译时常量变量或文本)。

Constexpr 函数也可以在运行时进行评估

#include <iostream>

constexpr int greater(int x, int y)
{
    return (x > y ? x : y);
}

int main()
{
    int x{ 5 }; // not constexpr
    int y{ 6 }; // not constexpr

    std::cout << greater(x, y) << " is greater!\n"; // will be evaluated at runtime

    return 0;
}

在此示例中,由于参数 和 不是常量表达式,因此无法在编译时解析该函数。但是,该函数仍将在运行时解析,将预期值作为 non-constexpr 返回。

根据 C++ 标准,如果在返回值的地方使用常量表达式,则必须在编译时计算符合编译时计算条件的 constexpr 函数。否则,编译器可以在编译时或运行时自由评估函数。

使用 consteval 使 constexpr 在编译时执行(C++20)

consteval 函数的缺点是此类函数无法在运行时进行计算,这使得它们不如 constexpr 函数灵活,后者可以执行任何操作。

#include <iostream>

// Uses abbreviated function template (C++20) and `auto` return type to make this function work with any type of value
// See 'related content' box below for more info (you don't need to know how these work to use this function)
consteval auto compileTime(auto value)
{
    return value;
}

constexpr int greater(int x, int y) // function is constexpr
{
    return (x > y ? x : y);
}

int main()
{
    std::cout << greater(5, 6) << '\n';              // may or may not execute at compile-time
    std::cout << compileTime(greater(5, 6)) << '\n'; // will execute at compile-time

    int x { 5 };
    std::cout << greater(x, 6) << '\n';              // we can still call the constexpr version at runtime if we wish

    return 0;
}

如果我们使用 constexpr 函数的返回值作为 consteval 函数的参数,则必须在编译时计算 constexpr 函数!consteval 函数只是将此参数作为自己的返回值返回,因此调用方仍然可以使用它。

std::string

先介绍一下 C 样式的字符串文字:

#include <iostream>

int main()
{
    std::cout << "Hello, world!"; // "Hello world!" is a C-style string literal.
    return 0;
}

虽然 C 样式的字符串文本很好用,但 C 样式的字符串变量行为奇怪,难以处理(例如,您不能使用赋值为 C 样式的字符串变量分配一个新值),并且很危险(例如,如果您将较大的 C 样式字符串复制到分配给较短的 C 样式字符串的空间中,则会导致未定义的行为)。

在现代 C++ 中,最好避免使用 C 样式的字符串变量。

幸运的是,C++在语言中引入了两种额外的字符串类型,它们更容易、更安全:
std::string std::string_view

在 C++ 中处理字符串和字符串对象的最简单方法是通过类型,该类型位于 标头中。

我们可以像创建其他对象一样创建类型的对象:

#include <string> // allows use of std::string

int main()
{
    std::string name {}; // empty string

    return 0;
}

就像普通变量一样,您可以按照预期初始化或为 std::string 对象赋值:

#include <string>

int main()
{
    std::string name { "Alex" }; // initialize name with string literal "Alex"
    name = "John";               // change name to "John"

    return 0;
}

字符串输出 std::cout

#include <iostream>
#include <string>

int main()
{
    std::string name { "Alex" };
    std::cout << "My name is: " << name << '\n';

    return 0;
}

在这里插入图片描述

std::string可以处理不同长度的字符串

#include <iostream>
#include <string>

int main()
{
    std::string name { "Alex" }; // initialize name with string literal "Alex"
    std::cout << name << '\n';

    name = "Jason";              // change name to a longer string
    std::cout << name << '\n';

    name = "Jay";                // change name to a shorter string
    std::cout << name << '\n';

    return 0;
}

字符串输入 std::cin

#include <iostream>
#include <string>

int main()
{
    std::cout << "Enter your full name: ";
    std::string name{};
    std::cin >> name; // this won't work as expected since std::cin breaks on whitespace

    std::cout << "Enter your favorite color: ";
    std::string color{};
    std::cin >> color;

    std::cout << "Your name is " << name << " and your favorite color is " << color << '\n';

    return 0;
}

在这里插入图片描述

用于输入文本std::getline()

#include <iostream>
#include <string> // For std::string and std::getline

int main()
{
    std::cout << "Enter your full name: ";
    std::string name{};
    std::getline(std::cin >> std::ws, name); // read a full line of text into name

    std::cout << "Enter your favorite color: ";
    std::string color{};
    std::getline(std::cin >> std::ws, color); // read a full line of text into color

    std::cout << "Your name is " << name << " and your favorite color is " << color << '\n';

    return 0;
}

不要按值传递

每当初始化 std::string 时,都会创建用于初始化它的字符串的副本。制作字符串的副本很昂贵,因此应注意尽量减少制作的副本数量。

当 a 按值传递给函数时,必须实例化函数参数并使用参数进行初始化。这会导致昂贵的副本。

Constexpr 字符串

尝试定义 ,编译器可能会生成错误:constexpr std::string

#include <iostream>
#include <string>

int main()
{
    using namespace std::string_literals;

    constexpr std::string name{ "Alex"s }; // compile error

    std::cout << "My name is: " << name;

    return 0;
}

发生这种情况是因为在 C++17 或更早版本中根本不支持,并且仅在 C++20/23 中非常有限的情况下有效。如果您需要 constexpr 字符串,请改用 std::string_view

std::string_view

为了解决初始化(或复制)字符串成本高昂的问题,引入了 C++17(位于 <string_view> 标头中)。 提供对现有字符串(C 样式字符串、a 或另一个字符串)的只读访问,而无需创建副本。

只读意味着我们可以访问和使用正在查看的值,但我们不能修改它。

下面有两个示例:

使用 std::string

#include <iostream>
#include <string>

void printString(std::string str) // str makes a copy of its initializer
{
    std::cout << str << '\n';
}

int main()
{
    std::string s{ "Hello, world!" }; // s makes a copy of its initializer
    printString(s);

    return 0;
}

使用 std::string_view

#include <iostream>
#include <string_view> // C++17

// str provides read-only access to whatever argument is passed in
void printSV(std::string_view str) // now a std::string_view
{
    std::cout << str << '\n';
}

int main()
{
    std::string_view s{ "Hello, world!" }; // now a std::string_view
    printSV(s);

    return 0;
}

该程序生成与前一个程序相同的输出,但不会创建字符串“Hello, world!”的副本。

当需要只读字符串时,首选 std::string_view,尤其是对于函数参数。

可以使用许多不同类型的字符串进行初始化

关于一个巧妙的事情之一是它的灵活性。

对象可以用 C 样式字符串、a 、 或其他字符串进行初始化:

#include <iostream>
#include <string>
#include <string_view>

int main()
{
    std::string_view s1 { "Hello, world!" }; // initialize with C-style string literal
    std::cout << s1 << '\n';

    std::string s{ "Hello, world!" };
    std::string_view s2 { s };  // initialize with std::string
    std::cout << s2 << '\n';

    std::string_view s3 { s2 }; // initialize with std::string_view
    std::cout << s3 << '\n';

    return 0;
}

可以接受许多不同类型的字符串参数

C 样式字符串和 a 都将隐式转换为 .因此,参数将接受 C 样式字符串:

#include <iostream>
#include <string>
#include <string_view>

void printSV(std::string_view str)
{
    std::cout << str << '\n';
}

int main()
{
    printSV("Hello, world!"); // call with C-style string literal

    std::string s2{ "Hello, world!" };
    printSV(s2); // call with std::string

    std::string_view s3 { s2 };
    printSV(s3); // call with std::string_view

    return 0;
}

std::string_view不会隐式转换为std::string

因为要复制它的初始化项(开销很大),c++不允许a到a的隐式转换。

这是为了防止意外地将实参传递给形参,并在可能不需要这种副本的情况下无意中生成昂贵的副本。

constexpr std::string_view

#include <iostream>
#include <string_view>

int main()
{
    constexpr std::string_view s{ "Hello, world!" }; // s is a string symbolic constant
    std::cout << s << '\n'; // s will be replaced with "Hello, world!" at compile-time

    return 0;
}

std::string_view最好用作只读函数参数

std::string_view std::string 区别

std::string 和 std::string_view 在 C++ 中都用于处理字符串,但它们的用法和性能有所不同:

  1. std::string 是一个动态字符串类,它可以创建、修改和删除字符串。std::string 会分配内存来存储字符串数据,因此在创建和修改字符串时可能会有一定的性能开销。

    std::string str = "Hello, World!";
    str += " How are you?";
    
  2. std::string_view 是 C++17 引入的一个新特性,它提供了一种引用字符串(或字符串的一部分)的轻量级方式,而无需复制字符串。std::string_view 不会分配内存,也不会拷贝字符串,因此在处理大字符串或者需要频繁修改字符串的场景下,std::string_view 可以提供更好的性能。

    std::string str = "Hello, World!";
    std::string_view sv = str;
    

需要注意的是,std::string_view 只是引用了字符串,而不拥有它。如果原始字符串被修改或删除,std::string_view 可能会引用到无效的内存。因此,std::string_view 最好只在你确定原始字符串不会被修改或删除的情况下使用。

std::string_view 和 std::string 哪个更适合用于函数参数传递

  1. 如果你的函数只需要读取字符串,不需要修改它,那么 std::string_view 是一个更好的选择。std::string_view 可以接受 std::string 和 C 风格字符串,而且不会产生额外的内存分配或字符串复制,因此性能更好。

    void print_string(std::string_view sv) {
     std::cout << sv << std::endl;
    }
    
  2. 如果你的函数需要修改字符串,或者需要保留字符串作为返回值或存储在数据结构中,那么你应该使用 std::string。std::string 拥有它的数据,因此你可以安全地修改它,而不用担心原始字符串被修改或删除。

    std::string to_upper(std::string str) {
      for (auto& c : str) {
    	    c = std::toupper(c);
    	}
     return str;
    }
    
  3. 如果你的函数需要以 null 结尾的字符串,例如需要传递给需要以 null 结尾的字符串的 C 函数,那么你应该使用 std::string。std::string 保证字符串以 null 结尾,而 std::string_view 不提供这个保证。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/420693.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于springboot+html实现的衣物捐赠平台

一、系统架构 前端&#xff1a;html | layui | jquery | css 后端&#xff1a;springboot | thymeleaf | mybatis 环境&#xff1a;jdk1.8 | mysql | maven 二、代码及数据库 三、功能介绍 01. 登录页 02. 注册 03. web页-首页 04. web页-捐赠衣服 05. web页-论坛交流…

Doris实战——金融壹账通指标中台的应用实践

目录 前言 一、业务痛点 二、早期架构挑战 三、架构升级 四、一体化指标数据平台 4.1 构建指标体系 4.2 构建指标平台功能 五、Doris指标应用实践 六、未来规划 原文大佬的这篇指标中台的应用实践有借鉴意义&#xff0c;这里摘抄下来用作学习和知识沉淀。 前言 在搭建…

开源项目_代码生成项目介绍

1 CodeGeeX 系列 1.1 CodeGeeX 项目地址&#xff1a;https://github.com/THUDM/CodeGeeX 7.6k Star主要由 Python 编写深度学习框架是 Mindspore代码约 2.5W 行有 Dockerfile&#xff0c;可在本地搭建环境模型大小为 150 亿参数相对早期的代码生成模型&#xff0c;开放全部代…

BAT等大厂必问技术面试题,2024Android开发面试解答之设计模式

IT行业薪水高&#xff0c;这是众所周知的&#xff0c;所以很多人大学都选择IT相关专业&#xff0c;即使非该专业的人&#xff0c;毕业了也想去一个培训机构镀镀金&#xff0c;进入这一行业。 但是有关这个行业35岁就退休的说法&#xff0c;也一直盛传。 加上这几年不断有各大…

基于java Springboot实现课程评分系统设计和实现

基于java Springboot实现课程评分系统设计和实现 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文末获取源…

【白嫖8k买的机构vip教程】Appium自动化(3):Appium-Desktop界面介绍

Appium-Desktop主界面包含三个菜单Simple、Advanced、Presets Simple界面&#xff1a; Host设置Appium server的ip地址&#xff0c;本地调试可以将ip地址修改为127.0.0.1&#xff1b;Port设置端口号&#xff0c;默认是4723不用修改Start Server 启动 Appium serverEdit Confi…

网络安全课程VIP介绍(比同行便宜)

免责声明 本文发布的工具和脚本&#xff0c;仅用作测试和学习研究&#xff0c;禁止用于商业用途&#xff0c;不能保证其合法性&#xff0c;准确性&#xff0c;完整性和有效性&#xff0c;请根据情况自行判断。如果任何单位或个人认为该项目的脚本可能涉嫌侵犯其权利&#xff0c…

(学习日记)2024.03.01:UCOSIII第三节

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

Java毕业设计-基于springboot开发的私人健身与教练预约系统-毕业论文+答辩PPT(有源代码)

文章目录 前言一、毕设成果演示&#xff08;源代码在文末&#xff09;二、毕设摘要展示1.开发说明2.需求分析3、系统功能结构 三、系统实现展示1、系统功能模块2、后台功能模块2.1管理员功能2.2用户功能2.3教练功能 四、毕设内容和源代码获取总结 [Java毕业设计-基于springboot…

零拷贝技术深入分析

一、零拷贝 在前面的文章“深浅拷贝、COW及零拷贝”中对零拷贝进行过分析&#xff0c;但没有举例子&#xff0c;也没有深入进行展开分析。本文将结合实际的例程对零拷贝进行更深入的分析和说明。 在传统的IO操作中&#xff0c;以文件通过网络传输为例 &#xff0c;一般会经历以…

【前端素材】推荐优质在线花卉商城电商网页Flowery平台模板(附源码)

一、需求分析 1、系统定义 在线花卉商城是一个通过互联网提供花卉销售服务的电子商务平台&#xff0c;用户可以在该平台上浏览、选择和购买各种花卉产品。 2、功能需求 在线花卉商城是一个通过互联网提供花卉销售服务的电子商务平台&#xff0c;用户可以在该平台上浏览、选…

内存取证 Volatility

文章目录 安装工具volatility和插件mimikatz[陇剑杯 2021]内存分析 内存分析工具 volatility&#xff0c;有Volatility2和Volatility3两种&#xff0c;分别基于Python2和Python3环境运行。说是一般Volatility2比Volatility3好用&#xff0c;所以我也选择的Volatility2版本。 一…

kubectl 陈述式资源管理方法

目录 陈述式资源管理方法 项目的生命周期 1.创建kubectl create命令 2.发布kubectl expose命令 service的4的基本类型 查看pod网络状态详细信息和 Service暴露的端口 查看关联后端的节点 ​编辑 查看 service 的描述信息 ​编辑在 node01 节点上操作&#xff0c;查看…

LeetCode 2120.执行所有后缀指令

现有一个 n x n 大小的网格&#xff0c;左上角单元格坐标 (0, 0) &#xff0c;右下角单元格坐标 (n - 1, n - 1) 。给你整数 n 和一个整数数组 startPos &#xff0c;其中 startPos [startrow, startcol] 表示机器人最开始在坐标为 (startrow, startcol) 的单元格上。 另给你…

前端的文字的字体应该如何设置

要设置文字的字体&#xff0c;在CSS中使用font-family属性。这个属性可以接受一个或多个字体名称作为其值&#xff0c;浏览器会按照列表中的顺序尝试使用这些字体渲染文本。如果第一个字体不可用&#xff0c;浏览器会尝试使用列表中的下一个字体&#xff0c;依此类推。 字体设…

SpringCloud gateway限流无效,redis版本低的问题

在使用springCloud gateway的限流功能的时候&#xff0c;配置RedisRateLimiter限流无效&#xff0c;后来发现是Redis版本过低导致的问题&#xff0c;实测 Redis版本为3.0.504时限流无效&#xff0c;改用7.0.x版本的Redis后限流生效。查了资料发现很多人都遇见过这个问题&#x…

让面试官眼前一黑,手把手带你打造个性化的 GitHub 首页

前期回顾 手机打开 第三方 “微信、快手、QQ、电话、信息” 等-CSDN博客https://blog.csdn.net/m0_57904695/article/details/136304084?spm1001.2014.3001.5501 &#x1f6a9;Github访问 Huo-zai-feng-lang-li (彩色之外) (github.com) &…

uniapp实现-审批流程效果

一、实现思路 需要要定义一个变量, 记录当前激活的步骤。通过数组的长度来循环数据&#xff0c;如果有就采用3元一次进行选择。 把循环里面的变量【name、status、time】, 全部替换为取出的那一项的值。然后继续下一次循环。 虚拟的数据都是请求来的, 组装为好渲染的格式。 二…

【python基础学习04课_python的字典】

字典 一、字典的定义 1、定义 字典&#xff1a;具有键值对 映射关系的一组无序的数据组合key: value key不变(不能够重复的&#xff0c;通常用str) value可变(可以用很多类型)通过key来找到对应的value标识符&#xff1a;{}关键字: dict无序&#xff1a;没有下标 2、打印…

Beans模块之工厂模块Aware

博主介绍:✌全网粉丝5W+,全栈开发工程师,从事多年软件开发,在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战,博主也曾写过优秀论文,查重率极低,在这方面有丰富的经验✌ 博主作品:《Java项目案例》主要基于SpringBoot+MyBatis/MyBatis-plus+…