C++学习笔记二

时间:2024-07-06 06:59:33

一、常量

1.用const关键字声明常量变量
  1. const常量变量在定义时必须进行初始化,并且不能通过赋值来改其值
const double gravity { 9.8 };  //首选在类型之前使用const
int const sidesInSquare { 4 }; // “east const”风格,可以,但不是首选

  1. const 变量可以从其他变量(包括非常量变量)初始化
#include <iostream>

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

    const int constAge { age }; // initialize const variable using non-const value

    age = 5;      // ok: age is non-const, so we can change its value
    constAge = 6; // error: constAge is const, so we cannot change its value

    return 0;
}
  1. 将函数参数设为常量可借助编译器的帮助来确保函数内部不会更改参数的值。然而,在现代 C++ 中,我们不会将值参数设为常量,因为我们通常不关心函数是否会更改参数的值(因为它只是一个副本,无论如何都会在函数结束时被销毁)。该const关键字还会给函数原型带来少量不必要的混乱。const按值传递时不要使用。
2. constexpr变量
  1. 编译时常量变量和运行时常量变量。只有编译时常量变量可用于常量表达式——运行时常量变量(和非常量变量)不能。由于编译时常量变量没有实际缺点,因此我们通常希望尽可能使用编译时常量
int a {5};//不是const
int b {a};//显然是运行时const(因为初始化器是非const)
const int c {5};//显然是编译时const(因为初始化器是常量表达式)

const int d {someVar};//不清楚这是运行时const还是编译时const
const int e{getValue()};//不清楚这是运行时const还是编译时const
  1. constexpr(“Constant expression”的缩写)变量始终是编译时常量。因此,必须使用常量表达式初始化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;
}
3. const 与 constexpr 对变量的含义
  1. 对于变量,“const”表示对象的值在初始化后不能更改。“Constexpr”表示对象必须具有编译时已知的值。
    constexpr 变量隐式为 const。Const 变量不隐式为 constexpr(带有常量表达式初始化器的 const 整型变量除外)。
  2. const
  • 定义:const 关键字用于声明一个变量为常量。一旦被初始化,其值不能被修改。
  • 作用域:const 可以用于修饰局部变量、全局变量、类成员变量和指针。
  • 编译时:const 变量的值在编译时不需要是已知的,它们可以在运行时初始化。
  1. constexpr
  • 定义:constexpr 关键字用于声明一个常量表达式,其值在编译时就可以确定。它用于更严格的编译时常量。
  • 作用域:constexpr 通常用于修饰变量和函数。
  • 编译时:constexpr 变量的值必须在编译时是已知的。
  • 函数:用 constexpr 修饰的函数意味着该函数可以在编译时求值,并且返回值也是一个常量表达式。
  1. const变量称为运行时常量,constexpr称为编译时常量。constexpr 关键字可以用于函数,而 const 不能。

二、条件运算符

  1. Conditional ?: c ? x : y If conditional c is true then evaluate x, otherwise evaluate y
  2. C++ 中唯一的三元运算符,因此有时也称为“三元运算符”。
  3. 由于 C++ 将大多数运算符的求值优先级置于条件运算符的求值之上,因此很容易使用条件运算符编写无法按预期求值的表达式。
#include <iostream>

int main()
{
    int x { 2 };
    int y { 1 };
    int z { 10 - x > y ? x : y };
    std::cout << z;

    return 0;
}
----------------------------------------------------------------------------
您可能期望它的计算结果为10 - (x > y ? x : y)(计算结果为8),但实际上它的计算结果为(10 - x) > y ? x : y(计算结果为2)。
  1. 第二和第三个操作数的类型必须匹配。编译器必须能够找到一种方法将第二个和第三个操作数中的一个或两个转换为匹配的类型。编译器使用的转换规则相当复杂,在某些情况下可能会产生令人惊讶的结果。

三、内联函数和变量

  1. 内联扩展是一个将函数调用替换为被调用函数定义中的代码的过程。这使我们能够避免这些调用的开销,同时保留代码的结果。
  2. 内联扩展最适合简单、短的函数(例如不超过几个语句),尤其是单个函数调用可以执行多次的情况(例如循环内的函数调用)。
  3. inline关键字,使用inline关键字声明的函数为内联函数
#include <iostream>

inline int min(int x, int y) // inline keyword means this function is an inline function
{
    return (x < y) ? x : y;
}

int main()
{
    std::cout << min(5, 6) << '\n';
    std::cout << min(3, 2) << '\n';
    return 0;
}
  1. C++17 引入了内联变量,即允许在多个文件中定义的变量。内联变量的工作方式与内联函数类似,并且具有相同的要求(编译器必须能够在使用该变量的任何地方看到相同的完整定义)。

四、std::string 简介

  1. std::string和std::string_view都可以用于定义字符串变量
#include <string> // allows use of std::string

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

    return 0;
}
  1. 最巧妙的事情std::string之一就是存储不同长度的字符串,字符串的最后一个字符是’\0’。如果std::string没有足够的内存来存储字符串,它将使用一种称为动态内存分配的内存分配形式(在运行时)请求额外的内存。这种获取额外内存的能力是其std::string如此灵活的部分原因,但也相对较慢。
  2. 使用std::cin读取字符串会返回遇到第一个空格之前的字符。其它的字符都在缓冲区内,等待下一次提取
    在这里插入图片描述
  3. std::ws 是 C++ 标准库中的一个流操作符,用于从输入流中提取和丢弃所有空白字符,直到遇到第一个非空白字符。它常用于处理用户输入以确保输入流中没有多余的空白字符干扰后续读取操作。
#include <iostream>
#include <string>

int main() {
    std::string input;
    std::cout << "Enter a string: ";
    std::cin >> std::ws; // 跳过所有前导空白字符
    std::getline(std::cin, input); // 读取输入
    std::cout << "You entered: " << input << std::endl;
    return 0;
}
-------------------------------------------------------------------
提取到变量时,提取运算符 ( >>) 会忽略前导空格。遇到非前导空格时会停止提取。

std::getline()不会忽略前导空格。如果您希望它忽略前导空格,请将其std::cin >> std::ws作为第一个参数传递。遇到换行符时,它会停止提取。

  1. 对于string字符串中的长度可以使用string input; cout<<input.length()<<endl;得出,因为length()成员函数在std::string有声明。它的长度不包含’\0’字符。
  • 如果要把它赋值给int变量,那么int length{static_cast<int>(input.length())};
  1. 范例
    编写一个程序,要求用户输入全名和年龄。输出是告诉用户他们的年龄总和和名字中的字母数(使用成员std::string::length()函数获取字符串的长度)。为简单起见,将名字中的任何空格都算作一个字母。

---------------------------------------------------------------------------------------
#include <iostream>
#include <string>

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
	//cin>>std::ws;std::getline(std::cin,name);等同于
	//
    std::cout << "Enter your age: ";
    int age{}; // age needs to be an integer, not a string, so we can do math with it
    std::cin >> age;

    // age is signed, and name.length() is unsigned -- we shouldn't mix these
    // We'll convert name.length() to a signed value
    int letters{ static_cast<int>(name.length()) }; // get number of letters in name (including spaces)
    std::cout << "Your age + length of name is: " << age + letters << '\n';

    return 0;
}

五、用户命名空间

1.关键字namespace
//语法如下,命名空间必须在全局范围内定义,或者在另一个命名空间内定义。与函数的内容非常相似,命名空间的内容通常缩进一级。
namespace NamespaceIdentifier 
{ 
    // 此处为命名空间的内容
}
  • 使用范围解析运算符 (:???? 访问命名空间,范围解析运算符告诉编译器应在左侧操作数的范围内查找右侧操作数指定的标识符。
#include <iostream>

namespace Foo // define a namespace named Foo
{
    // This doSomething() belongs to namespace Foo
    int doSomething(int x, int y)
    {
        return x + y;
    }
}

namespace Goo // define a namespace named Goo
{
    // This doSomething() belongs to namespace Goo
    int doSomething(int x, int y)
    {
        return x - y;
    }
}

int main()
{
    std::cout << Foo::doSomething(4, 3) << '\n'; // use the doSomething() that exists in namespace Foo
    std::cout << Goo::doSomething(4, 3) << '\n'; // use the doSomething() that exists in namespace Goo
    return 0;
}
  • 范围解析运算符也可以用在标识符前面,而无需提供命名空间名称(例如::doSomething)。在这种情况下,doSomething将在全局命名空间中查找标识符。
  • 如果使用命名空间内的标识符且未提供范围解析,则编译器将首先尝试在同一命名空间中查找匹配的声明。 如果未找到匹配的标识符,则编译器将依次检查每个包含的命名空间以查看是否找到匹配项,最后检查全局命名空间。

在这里插入图片描述
使用 ::print() 时,编译器只会在全局命名空间中寻找 print 函数。如果全局命名空间中不存在 print 函数,则会报错。不会在其他命名空间中寻找名为 print 的函数。命名空间有全局和定义的命名空间之分。

2、命名空间嵌套
#include <iostream>

namespace Foo
{
    namespace Goo // Goo is a namespace inside the Foo namespace
    {
        int add(int x, int y)
        {
            return x + y;
        }
    }
}

int main()
{
    std::cout << Foo::Goo::add(1, 2) << '\n';
    return 0;
}
--------------------------------------------------------
请注意,由于命名空间Goo位于命名空间内部Foo,因此我们add以身份访问Foo::Goo::add。
  • 命名空间别名 namespace Active = Foo::Goo; // active now refers to Foo::Goo
  • 允许跨文件或者同一文件的多个位置声明同一个命名空间,命名空间内的所有声明均视为命名空间的一部分。

编译和链接

  1. Linux(或其他操作系统)上编译 C++ 程序时,链接步骤在以下情况下是必需的:
  • 多个源文件:当你的程序由多个源文件(.cpp 文件)组成时,需要将这些文件编译并链接在一起。例如,如果你有一个 main.cpp 和一个 add.cpp,你需要将它们都编译并链接成一个可执行文件。

  • 使用外部库:如果你的程序依赖于一个或多个外部库(例如,Boost 库、SQLite 库等),你需要在链接阶段指定这些库,以便将库中的函数和对象链接到你的程序中。

  • 使用头文件和实现分离:当你将函数声明放在头文件(.h 文件)中,而将函数定义放在源文件(.cpp 文件)中时,需要在链接阶段将编译后的目标文件(.o 文件)链接在一起。

  1. 编译和链接的步骤
    编译和链接可以分为两个主要步骤:
  • 编译:将每个源文件(.cpp 文件)编译成目标文件(.o 文件)。
  • 链接:将所有目标文件(.o 文件)和库文件链接在一起,生成最终的可执行文件。
  1. 示例
    假设我们有三个文件:main.cpp、add.h 和 add.cpp。
  • 编译源文件
g++ -c main.cpp -o main.o//编译main.cpp为main.o
g++ -c add.cpp -o add.o //编译add.cpp为add.o
  • 链接目标文件
g++ main.o add.o -o main//将main.o和add.o链接后输出为main可执行文件
  • 一步完成上诉所有步骤
g++ -o main main.cpp add.cpp
g++ main.cpp add.cpp//此时可执行文件默认为a.out
  • 链接外部库
g++ -o main main.cpp add.cpp -lm