C++ 学习9(10.10-10.11)

149 阅读8分钟

1.1 字符串(string)

C++ 中的字符串是通过 std::string 类实现的,它是 C++ 标准库提供的字符串类。使用 std::string 类时,需要包含头文件 。

#include <iostream>
#include <string>

int main()
{
   std::string name = std::string("Cherno") + " hello";//初始化+拼接

   name += "  world  ";          //赋值与拼接
   std::cout << " 修改后的 name : " << name << std::endl;

   int size = name.size();
   int length = name.length();     //获取字符串的长度
   char ch = name[0];         //访问字符串中的字符

   std::cout<<"size length ch:"<<size<<" "<<length<<" "<<ch<<std::endl;
//   const char* name = "Cherno";
   char name2[9] = { 'c','h','e','r','n','o' };
   std::cout << "name2 : " << name2 << std::endl;
   if( name > name2)    //字符串比较
      std::cout << "name > name2" << std::endl;
   else
      std::cout << "name2 > name" << std::endl;

   size_t index = name.find("hello");//查找子字符串的位置
   name.replace(index, 5, "hi");//替换子字符串
   std::cout <<"find & replace : "<<index<<" "<< name << std::endl;


   std::cin.get();
}

结果:

关于字符串的比较:

是比较相应的位置的字符的ASCII码的大小。

#include <iostream>
#include <string>

int main(){

   std::string str1 = "AbA";//b :ascii 98
   std::string str2 = "AB";//B :ascii 66

//int result = str1.compare(str2);

   if(str1 == str2)
   {
    // 字符串相等
      std::cout << " = " <<std::endl;
   }
   else if (str1 < str2)
   {
    // str1 小于 str2
      std::cout << " < " << std::endl;
   }
   else
   {
    // str1 大于 str2
      std::cout << " > " << std::endl;
   }
   return 0;
}

结果:

字符串字面量(string literal)

C++ 中的字符串字面量是一个数组,存储在内存中,并以空字符 (\0) 结束。

std::string str = "Hello, world!";      上述例子中的字符串字面量 "Hello, world!" 实际上是一个长度为 13 的字符数组,它的最后一个元素是空字符。

Release 模式

       Release 模式是指在编译程序时使用了优化(optimization)选项,从而生成更高效、更快速的可执行文件。在 Release 模式下,编译器会对代码进行一系列的优化处理,以提高程序的运行速度和代码执行效率。
相比之下,Debug 模式是指在编译程序时不使用优化选项,以便在调试时方便开发人员查看和修改代码,但是会导致程序运行速度较慢。

在 Release 模式下,编译器一般采用以下优化技术:

  1.  前向跳转(forward jump):将多余的代码移动到循环外部或函数之外,避免重复计算。
  2. 内联函数(inline function):将函数调用替换为函数体,减少函数调用时间。
  3. 去除函数框架(function prologue/epilogue):简化函数调用时的堆栈操作,减少函数调用时间。
  4.  编译时常量(compile time constants):在编译时计算常量表达式,避免运行时的计算,提高效率。

但是,也要注意到 Release 模式下编译器进行的优化也可能会带来一些问题,例如优化后的代码可能不易于调试,或者存在一定的安全风险。因此,在进行 Release 模式编译时,需要权衡优化和安全性之间的关系,并根据实际需要选择合适的优化选项。

#include <iostream>
#include <string>
#include <stdlib.h>

int main()
{
  // char name[] = "Cherno";
//   name[2] = 'a';
   //std::cout << name << std::endl;
   std::string name = "Cherno";
  // name.replace(4,1,"c");
//   std::cout << name << std::endl;
   name.replace(1,4,"abcd");


   std::cout << name << std::endl;

   std::cin.get();
}

结果:

Release 优化

g++ -O3 stringliteral stringliteral.cpp

对比:

time g++ -O0 stringliteral.cpp -o stringliteral_no_optimization
time g++ -O3 stringliteral.cpp -o stringliteral_optimization

结果对比图:

其中,real 行显示的是实际经过的时间,user 行显示的是 CPU 用户态时间,sys 行显示的是 CPU 内核态时间。

  1. real 行显示的是从外部观察到的程序执行时间,包括启动程序、读取和写入数据、等待 I/O 以及处理其它系统相关事务等所有时间。这是用户最关心的时间指标。 

  2. user 行显示的是程序在用户态(即应用程序的运行状态)下消耗的 CPU 时间。

  3. sys 行显示的是程序在内核态(即操作系统的运行状态)下消耗的 CPU 时间。

1.2 const 关键字

C++ 中的 const 是一个关键字,用于指定某个值或对象为只读,即它们的值不能被修改。const 可以用于多种场合,如函数参数、函数返回值、变量定义等。

  1. 使用 const 定义常量。

  2. 使用 const 修饰函数参数。

    int add(const int x, const int y) {
        return x + y;
    }//两个参数 x 和 y 都被声明为 const,这意味着它们的值在函数执行过程中不能被修改。
    
  3. 使用 const 修饰函数返回值。

    const char* getMessage() {
        return "Hello, world!";
    }//函数 getMessage 的返回值被声明为 const char*,
    //这意味着在函数外部不能通过返回值修改字符串的内容。
    
  4. 使用 const 修饰成员函数。

    class MyClass {
    public:
        void print() const {
            // ...
        }//类 MyClass 中的成员函数 print 被声明为 const,
    //这意味着它不能修改类的成员变量和调用非 const 成员函数。
    };
    

代码示例:

#include <iostream>
// 4 类成员函数为常量
class Circle {
public:
    Circle(double radius) : m_radius(radius) {}

    double getArea() const {
        // m_radius = 5.0;  // 编译错误,常量成员函数不能修改类成员变量
        return 3.14159 * m_radius * m_radius;
    }

private:
    const double m_radius;
};
// 3   const 修饰函数返回值
const int getValue() {
    return 42;
}
//  2   函数参数为常量
void printNumber(const int num) {
    // num = 10;  // 编译错误,函数参数为常量,不可修改
    std::cout << "Number: " << num << std::endl;
}
int main() {
//    1  常量定义
    const int MAX_SIZE = 100;
    // MAX_SIZE = 200;  // 编译错误,常量值不能修改
    std::cout << "Max size: " << MAX_SIZE << std::endl;
// 2  常量参数
    int number = 42;
    printNumber(number);//函数参数是常量
//   3  常量函数
    const int value = getValue();
    // value = 100;  // 编译错误,常量值不能修改

    std::cout << "Value: " << value << std::endl;
//  4  常量成员函数
    Circle circle(2.0);
    std::cout << "Area: " << circle.getArea() << std::endl;
    return 0;
}

结果:

关于;

// 4 类成员函数为常量
class Circle {
public:
    Circle(double radius) : m_radius(radius) {}

    double getArea() const {
        // m_radius = 5.0;  // 编译错误,常量成员函数不能修改类成员变量
        return 3.14159 * m_radius * m_radius;
    }
private:
    const double m_radius;
};

解析:Circle的类,该类具有一个     常量成员变量m_radius 和一个  常量成员函数getArea()。 在类的构造函数中,我们使用   初始化列表语法 将传入的半径值radius赋值给常量数据成员m_radius。需要注意的是,由于m_radius是一个常量成员变量,我们只能在构造函数中对其进行初始化,而不能在其他函数中修改其值。 在常量成员函数getArea()中,我们使用公式计算圆的面积,并返回结果。需要注意的是,由于getArea()是一个常量成员函数,我们不能在其中修改类的数据成员,包括常量成员变量和非常量成员变量。如果尝试修改类的数据成员,则会导致编译错误。

初始化列表法:

class MyClass {
public:
    MyClass(int a, int b) : memberA(a), memberB(b) {
        // 构造函数的函数体
    }

private:
    int memberA;
    int memberB;
};

解析:名为MyClass的类,具有两个整型成员变量memberA和memberB。在构造函数中,使用初始化列表对这两个成员变量进行初始化。初始化列表(: memberA(a), memberB(b))指明了使用参数a来初始化memberA,使用参数b来初始化memberB。

使用初始化列表的好处包括:

  1.  可以有效地避免在构造函数体中对成员变量赋值,提高效率。 
  2. 支持对常量成员变量进行初始化。 
  3. 可以初始化引用类型的成员变量。

需要注意的是,初始化列表的顺序应该与成员变量的声明顺序保持一致,否则可能导致未定义的行为。同时,对于常量成员变量、引用类型的成员变量或者具有自定义构造函数的成员变量,初始化列表是必须的。

**mutable:**允许函数是常量函数,但是可以被修改。

#include <iostream>
#include <string>

class Entity
{
private:
   int m_X, m_Y;
   mutable int var;//mutable 允许函数是常量方法,但是可以被修改

public:
   int GetX() const
   {
      var = 2;
      return m_X;
   }
   void SetX(int x)
   {
      m_X = x;
   }
};

void PrintEntity(const Entity& e)
{
   std::cout << e.GetX() << std::endl;
}


int main()
{
   const int MAX_AGE = 90;

   const int* const a = new int(42);

   std::cout << "MAX_AGE:" << MAX_AGE << std::endl;
   std::cout << "a: " << *a <<std::endl;

   delete a;
   return 0;
}

解析:

class Entity
{
private:
   int m_X, m_Y;
   mutable int var;//mutable 允许函数是常量方法,但是可以被修改

public:
   int GetX() const
   {
      var = 2;
      return m_X;
   }
   void SetX(int x)
   {
      m_X = x;
   }
};

定义了一个名为 Entity 的类,具有私有成员变量 m_X 和 m_Y,以及可变成员变量 var。 GetX() 函数是一个常量成员函数,使用 const 关键字进行修饰。在常量成员函数中,对于不会修改对象状态的操作,可以使用 mutable 关键字修饰的成员变量进行修改。在这里,var 是一个可变成员变量,即使在常量成员函数内部,也可以对其进行修改。GetX() 函数将 var 设置为2,并返回 m_X 的值。 SetX() 函数是一个普通的成员函数,用于设置 m_X 的值。

注意:

一个常成员函数,它有以下限制:

  1.  在常成员函数内部,不能修改类的非可变成员变量。也就是说,在 GetX() 函数中,不能直接修改 m_X 和 m_Y 的值,除非它们被声明为 mutable。
  2.  不能调用类的其他非常成员函数,除非它们也是常成员函数。常成员函数只能调用其他常成员函数,以保证在常成员函数内部不会修改对象的状态。
  3.  常成员函数只能返回常量或静态数据成员,或者通过引用或指针返回非常对象的常量引用或常量指针。这是为了防止通过常成员函数修改对象的状态。

 常成员函数的设计初衷是为了让类的成员函数能够在不修改对象状态的情况下访问对象的数据。通过使用常成员函数,可以确保类的接口不会意外地修改数据,增加代码的可靠性和安全性。

解析:

void PrintEntity(const Entity& e)
{
   std::cout << e.GetX() << std::endl;
}

  名为 PrintEntity() 的函数,它接受一个 const 引用 e,该引用指向 Entity 对象。 在函数内部,调用了 e 的常成员函数 GetX(),并将其返回值输出到标准输出流中,随后输出一个换行符。
由于 e 是一个常量引用,因此不能通过它修改对象的状态。而 GetX() 函数是一个常成员函数,可以安全地访问对象的数据,而不会对对象的状态造成影响。                               

这个函数的作用是输出 Entity 对象的 m_X 值,它只需要访问 Entity 对象的 m_X 数据成员,因此不需要改变对象的状态,所以使用了 const 修饰,避免误操作。

不能修改对象状态指的是:
对于某个对象而言,不能改变该对象的数据成员的值或属性。这通常是通过将对象声明为常量(const)来实现的。 当一个对象被声明为常量时,意味着其数据成员在常量对象上是只读的,不能被修改。常量对象在其生命周期内保持不变,任何试图修改常量对象数据成员的操作都会导致编译时错误。

解析:

int main()
{
   const int MAX_AGE = 90;
   const int* const a = new int(42);
   std::cout << "MAX_AGE:" << MAX_AGE << std::endl;
   std::cout << "a: " << *a <<std::endl;
   delete a;
   return 0;
}

定义了一个指针 a,它指向一个 int 类型的常量对象,该对象的值为42。这里使用了两个 const 关键字,第一个 const 表示指针本身是一个常量,即不能通过该指针修改所指向的对象。第二个 const 表示指针所指向的对象是一个常量,即不能通过该指针修改该对象的值。