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 xprintInt(6); // 6 will be used as the initializer for xreturn 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 expressionconstexpr int sum { 4 + 5 };      // ok: 4 + 5 is a constant expressionconstexpr int something { sum };  // ok: sum is a constant expressionstd::cout << "Enter your age: ";int age{};std::cin >> age;constexpr int myAge { age };      // compile error: age is not a constant expressionconstexpr int f { five() };       // compile error: return value of five() is not a constant expressionreturn 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 lessonconstexpr int g { greater(x, y) }; // will be evaluated at compile-timestd::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 6std::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 constexprint y{ 6 }; // not constexprstd::cout << greater(x, y) << " is greater!\n"; // will be evaluated at runtimereturn 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-timestd::cout << compileTime(greater(5, 6)) << '\n'; // will execute at compile-timeint x { 5 };std::cout << greater(x, 6) << '\n';              // we can still call the constexpr version at runtime if we wishreturn 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::stringint main()
{std::string name {}; // empty stringreturn 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 stringstd::cout << name << '\n';name = "Jay";                // change name to a shorter stringstd::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 whitespacestd::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::getlineint main()
{std::cout << "Enter your full name: ";std::string name{};std::getline(std::cin >> std::ws, name); // read a full line of text into namestd::cout << "Enter your favorite color: ";std::string color{};std::getline(std::cin >> std::ws, color); // read a full line of text into colorstd::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 errorstd::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 initializerprintString(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_viewprintSV(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 literalstd::cout << s1 << '\n';std::string s{ "Hello, world!" };std::string_view s2 { s };  // initialize with std::stringstd::cout << s2 << '\n';std::string_view s3 { s2 }; // initialize with std::string_viewstd::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 literalstd::string s2{ "Hello, world!" };printSV(s2); // call with std::stringstd::string_view s3 { s2 };printSV(s3); // call with std::string_viewreturn 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 constantstd::cout << s << '\n'; // s will be replaced with "Hello, world!" at compile-timereturn 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 不提供这个保证。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

v69.字符

1.字符类型 1.1 可以将char类型的变量赋值为整数&#xff0c;也可以赋值为字符! 注意字符要用单引号 ’ ’ 而不是双引号 每一个字符在计算机内部都有一个值去表达它。字符’1’ 在计算机里表示的十进制的整数值为49&#xff0c;就像’A’表示十进制值65。 1.2 scanf 与 p…

C++面试宝典第33题:数组组成最大数

题目 给定一组非负整数nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。 示例1: 输入:nums = [10, 2] 输出:"210" 示例2: 输入:nums = [3, 30, 34, 5, 9] 输出:"…

【查漏补缺你的Vue基础】Vue数据监听深度解析

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

【rust】10 project、crate、mod、pub、use、项目目录层级组织、概念和实战

文章目录 一、项目目录层级组织概念1.1 cargo new 创建同名 的 Project 和 crate1.2 多 crate 的 package1.3 mod 模块1.3.1 创建嵌套 mod1.3.2 mod 树1.3.3 用路径引用 mod1.3.3.1 使用绝对还是相对? 1.3.4 代码可见性1.3.4.1 pub 关键字1.3.4.2 用 super 引用 mod1.3.4.3 用…

阿里Java开发手册(黄山版) LeetCode刷题手册 免费下载

目录 一、阿里Java开发手册(黄山版) 二、LeetCode刷题手册 三、获取方式 今天给大家推荐两个程序员的辅助利器&#xff01;都是平时开发&#xff0c;刷算法能经常用到的书籍&#xff0c;怕百度云分享会失效&#xff0c;获取方式在最下面&#xff0c;永久有效。 一、阿里Jav…

C++ 反向迭代器的设计与实现

在本文开始之前&#xff0c;先明晰几个 关键词 的含义&#xff08;T : 模板参数&#xff09;&#xff1a; Ref : T& / const T&Ptr : T* / const T* 一、反向迭代器设计的上帝视角 我们希望将 反向迭代器 设计成一种适配器——传 list::iterator 得到 list 的反向迭代…

Lastools工具使用

Lastools工具使用 1、介绍 官网链接 常用的功能 las2las可以进行点云拼接las2txt可以将点云中的有效信息转换为txt文档&#xff08;如xyz坐标&#xff09;lasmerge可以将多个LAS/LAZ文件合并为一个文件Laszip将LAS文件高速压缩到LAZ而不会丢失信息las2text将LAS/LAZ转换为可…

C++初阶:模版相关知识的进阶内容(非类型模板参数、类模板的特化、模板的分离编译)

结束了常用容器的介绍&#xff0c;今天继续模版内容的讲解&#xff1a; 文章目录 1.非类型模版参数2.模板的特化2.1模版特化引入和概念2.2函数模版特化2.3类模板特化2.3.1全特化2.3.1偏特化 3. 模板分离编译3.1分离编译概念3.2**模板的分离编译**分析原因 1.非类型模版参数 模板…

设计模式(五)-观察者模式

前言 实际业务开发过程中&#xff0c;业务逻辑可能非常复杂&#xff0c;核心业务 N 个子业务。如果都放到一块儿去做&#xff0c;代码可能会很长&#xff0c;耦合度不断攀升&#xff0c;维护起来也麻烦&#xff0c;甚至头疼。还有一些业务场景不需要在一次请求中同步完成&…

如何在Linux系统Docker部署Wiki.js容器并结合内网穿透实现远程访问本地知识库

文章目录 1. 安装Docker2. 获取Wiki.js镜像3. 本地服务器打开Wiki.js并添加知识库内容4. 实现公网访问Wiki.js5. 固定Wiki.js公网地址 不管是在企业中还是在自己的个人知识整理上&#xff0c;我们都需要通过某种方式来有条理的组织相应的知识架构&#xff0c;那么一个好的知识整…

适用Java SpringBoot项目的分布式锁

在分布式系统中&#xff0c;常用到分布式锁&#xff0c;它有多中实现方式&#xff0c;如&#xff1a;基于redis&#xff0c;database&#xff0c;zookeeper等。Spring integration组件有这三种服务的分布式锁实现&#xff0c;今天来看看用的比较多的redis和database实现方式。 …

【EI会议征稿通知】2024年第三届生物医学与智能系统国际学术会议(IC-BIS 2024)

2024年第三届生物医学与智能系统国际学术会议&#xff08;IC-BIS 2024&#xff09; 2024 3rd International Conference on Biomedical and Intelligent Systems (IC-BIS 2024) 2024年第三届生物医学与智能系统国际学术会议&#xff08;IC-BIS 2024&#xff09; 将于2024年4月…

为什么推荐使用ref而不是reactive

为什么推荐使用ref而不是reactive 局限性问题&#xff1a; reactive本身存在一些局限性&#xff0c;可能会在开发过程中引发一些问题。这需要额外的注意力和处理&#xff0c;否则可能对开发造成麻烦。数据类型限制&#xff1a; reactive声明的数据类型仅限于对象&#xff0c;而…

Html零基础入门教程(非常详细)

文章目录 1.认识HTML2.html 框架3.HTML常见标签4.HTML语法特征5.列表 1.认识HTML html是超文本标记语言: 目前最新版本是html5,由w3c(万维网联盟)完成标准制定。 声明文档的类型是html5 超文本标记语言。 HTML &#xff0c;全称“Hyper Text Markup Language&#xff08;超文…

LNMP架构(搭建论坛+博客)

目录 一、LNMP架构概述 1、LNMP架构的概念 2、LNMP架构的优点 二、编译安装nginx软件 1、准备工作 1.1 关闭防火墙 1.2 安装依赖包 1.3 创建运行nginx用户 1.4 压缩包解压 2、编译与安装 3、添加nginx自启动文件 三、编译安装mysql软件 1、准备工作 1.1 安装mysq…

利用R语言进行聚类分析实战(数据+代码+可视化+详细分析)

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

width:100%和width:auto有啥区别

项目中使用了with属性&#xff0c;突然好奇auto 和 100% 的区别&#xff0c;特地搜索实践总结了一下观点 一、 width属性介绍二、 代码带入三、 分析比较四、 总结 一、 width属性介绍 width 属性用于设置元素的宽度。width 默认设置内容区域的宽度&#xff0c;但如果 box-siz…

Vue3切换路由白屏刷新后才显示页面内容

1.首先检查页面路由以及页面路径配置是否配置错误。 在router-view 中给路由添加key标识。 &#xff01;&#xff01;注意&#xff1a;有使用layout封装布局的&#xff0c;是在layout下的主页面中的 router-view 添加标识&#xff0c;不是在src根目录下main.vue中修改&#xf…

基于vue-office实现docx、xlsx、pdf文件的在线预览

概述 在做项目的时候会遇到docx、xlsx、pdf等文件的在线预览需求&#xff0c;实现此需求可以有多种解决方式&#xff0c;本文基于vue-office实现纯前端的文件预览。 效果 如下图&#xff0c;分别为docx、xlsx、pdf三种类型的文件在线加载后的效果。你也可以访问官方预览网址…

sawForceDimensionSDK安装,sigma7+ros

force dimension的sdk中没有关于ros&#xff0c;借助开源的sawForceDimensionSDK实现对于数据的封装和可视化&#xff0c;方便后续使用 链接&#xff1a; GitHub - jhu-saw/sawForceDimensionSDK 具体步骤&#xff1a; 安装qt和ros&#xff0c;官网下载Force Dimension SDK …