C++语法篇做题笔记

179 阅读11分钟
  • swap用于交换两个输入的值 swap(a,b);

  • 三元表达式从最外面的括号开始判断 image.png

  • image.png
  • setiosflags 常用的标志位有:

    std::ios::showpoint: 即使小数部分为0,也显示小数点。 std::ios::showpos: 显示正数的正号。 std::ios::skipws: 跳过输入流中的空白字符。 std::ios::uppercase: 在输出科学计数法时,使用大写字母(E 而不是 e)。 std::ios::fixed: 使用定点表示法显示浮点数,而不是科学计数法。 std::ios::scientific: 使用科学计数法显示浮点数。

  • 判断多区间多用switch,如果比较少的话会用三元表达式

  • 在case中,可以用x ... y 表示范围在[x,y]的值,两边都是闭区间

  • atol 是 C++ 标准库中的一个函数,用于将字符串转换为长整型(long)数值。这个函数位于 <cstdlib><stdlib.h> 头文件中。其原型如下:

long atol(const char *str);

在C++中,string(10, '1')是一个构造std::string对象的表达式,它创建了一个包含10个字符'1'的字符串。这不是一个函数,而是一个构造函数调用,用于初始化一个std::string对象。

std::string是C++标准库中的一个类,用于表示和处理字符串。当你使用string(10, '1')时,实际上是在调用std::string类的构造函数,创建了一个含有10个字符'1'的字符串对象。

接下来,.c_str()是一个std::string类的成员函数,它的作用是将std::string对象转换为一个以空字符('\0')结尾的C风格字符串。C风格字符串是一个字符数组,它以一个特殊的空字符作为字符串的结束标志。这种字符串格式在C语言和C++中被广泛使用,尤其是在需要与C语言兼容的接口时。

所以,string(10, '1').c_str()这个表达式的意思是:

  1. 使用std::string的构造函数创建一个包含10个字符'1'的字符串对象。
  2. 调用这个字符串对象的.c_str()成员函数,将std::string对象转换为C风格字符串。

这样,你就可以使用这个C风格字符串作为参数传递给那些需要C风格字符串的函数,例如atol

  • cout<<fixed<<setprecision(1)<<dist-h<<" "<<h<<endl;这句是1. fixed:这是流操纵符,用于设置浮点数的输出格式为固定小数点表示法,而不是科学计数法。
  1. setprecision(1):这也是一个流操纵符,用于设置小数点后的位数。在这个例子中,它将小数点后的位数设置为1位。
  • getline 是 C++ 标准库中的一个函数,用于从输入流中读取一行文本直到遇到换行符 \n 或输入结束。这个函数通常与 std::string 类型一起使用,来获取用户输入的一行文本。

函数的原型如下:

istream& getline(istream& is, string& str);
  • is 是输入流的引用,可以是 std::cinstd::ifstream 或任何其他 istream 对象。
  • str 是一个 std::string 对象的引用,用于存储读取的文本。

getline 函数会读取输入流,直到遇到换行符,并将读取的文本存储在 str 中。换行符本身不会被存储在 str 中。

  • C++11里面的for (const auto &item: arr)是如何工作的

假设用户输入了以下字符串:

input: "apple"

第一个循环:遍历字符串 s 在第一个循环中,我们遍历输入的字符串 s。对于上面的输入,循环将依次处理每个字符:

  1. item'a'
  2. item'p'
  3. item'p'
  4. item'l'
  5. item'e'

对于每个字符 item,我们执行 arr[item - 'a']++。这个表达式做了什么?它将字符转换为一个数字,方法是从字符的ASCII值中减去 'a' 的ASCII值。这样,字符 'a' 会映射到索引 0,'b' 映射到 1,依此类推。然后,我们增加数组 arr 中相应索引的值。

  • 对于 'a'arr[0] 增加 1。
  • 对于 'p'arr[15] 增加 1(因为 'p' - 'a' 等于 15)。
  • 再次遇到 'p'arr[15] 再次增加 1。
  • 对于 'l'arr[11] 增加 1。
  • 对于 'e'arr[4] 增加 1。

第二个循环:遍历数组 arr

在第二个循环中,我们遍历数组 arr。数组 arr 的每个元素都对应一个字母出现的次数。在我们的例子中,数组 arr 将如下所示:

  • arr[0] = 1 ('a' 出现 1 次)
  • arr[15] = 2 ('p' 出现 2 次)
  • arr[11] = 1 ('l' 出现 1 次)
  • arr[4] = 1 ('e' 出现 1 次)
  • 其他索引的值都是 0,因为没有其他字母出现。

循环将依次打印数组 arr 中的每个值,后面跟着一个空格:

  • 打印 arr[0](1)
  • 打印空格
  • 打印 arr[1](0)
  • ...
  • 打印 arr[15](2)
  • 打印空格
  • 打印 arr[16](0)
  • ...
  • 打印 arr[25](0)

输出结果 最终,程序将输出以下内容,表示每个字母出现的次数:

1 0 2 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0

这个输出表示 'a' 出现了 1 次,'p' 出现了 2 次,'l''e' 各出现了 1 次,其他字母没有出现。每个数字后面都有一个空格,除了最后一个数字。 int arr[3]{};

for (const auto &item: s) { arr[item-'a']++; }

for (const auto &item: arr) { cout<<item<<' '; }

  • //新建数组 int* p=new int[n];

在上述代码中,使用 int* p = new int[n]; 来创建一个动态数组,这里的 new 关键字是C++中用于动态内存分配的操作符。使用指针 p 来引用这个动态分配的数组,有以下几个原因:

  1. 动态大小new 允许在运行时指定数组的大小,这使得程序更加灵活。用户输入的 n 决定了数组的大小,而不是在编译时就固定。
  2. 内存分配new 操作符在堆上分配内存,而不是在栈上。这意味着可以使用比栈上更大的内存块,并且内存的使用不会受到函数调用栈大小的限制。
  3. 指针的使用:在C++中,动态分配的内存需要通过指针来访问。指针提供了一种方式来间接访问内存地址,这在需要操作大块数据或者实现某些数据结构(如链表、树等)时非常有用。
  4. 内存管理:使用 new 分配的内存需要显式地使用 delete 来释放。这是C++中手动内存管理的一部分,允许程序员精确控制内存的生命周期,避免内存泄漏。
  5. 数组操作:指针允许对数组进行操作,如通过索引访问元素。在上述代码中,p[i] 就是使用指针来访问数组的第 i 个元素。
  6. 通用性:指针提供了一种通用的方式来访问不同类型的数据结构,不仅限于数组。它们可以用于实现更复杂的数据结构和算法。

二维数组

在这段 C++ 代码中,二维数组是通过指针数组来实现的。具体来说,int **arr 定义了一个指向整型指针的指针,它用来指向一个整型指针数组,每个整型指针又指向一个整型数组。下面是代码中创建和使用二维数组的步骤:

  1. int **arr = new int*[n]; 这行代码分配了一个指针数组,每个指针指向一个大小为 n 的整型数组。

  2. for(int i = 0; i < n; i++) { arr[i] = new int[n]; } 这个循环为每个指针分配了一个大小为 n 的整型数组。

  3. 接下来的嵌套循环 for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { arr[i][j] = i + j; } } 用于初始化这个二维数组,其中 arr[i][j] 表示二维数组的第 i 行第 j 列的元素。

  4. 打印二维数组的循环 for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { cout << arr[i][j] << " "; } cout << endl; } 用于输出数组中的每个元素。

  5. 最后,释放二维数组内存的循环 for(int i = 0; i < n; i++) { delete[] arr[i]; } 用于释放每个指针指向的整型数组的内存。

  6. arr = NULL; 将指针数组的指针设置为 NULL,表示它不再指向任何内存,这是一个好习惯,但在这个特定情况下不是必需的,因为程序结束时所有的动态分配的内存都会被自动释放。

注意:在实际使用中,通常建议使用 std::vector 或者数组的数组(即数组的数组)来避免手动管理内存,这样可以减少内存泄漏的风险。

  • 字符相减默认用的是ASCII表中的值相减
  • cin.getline 得到的是字符数组,不是字符串,最后结尾还有一个\0
  • 不死神兔问题是斐波那契数列问题
  • 引用符号是&,也是取地址符
  • 不加地址符:传递的是变量的值,swap 交换的是这些值的副本,原始变量的值不会被改变。
  • 加了地址符:传递的是变量的引用,swap 交换的是这些引用所指向的内存位置的值,原始变量的值会被交换。

深拷贝(构造函数)

Person(const Person& p){ //拷贝构造函数

this->name = new char[strlen(p.name) + 1]; //设置name字符数组的长度

strcpy(this->name, p.name); //拷贝name数组

this->age = p.age; }这段代码是什么意思为什么要这么做

这段代码定义了一个拷贝构造函数,它是一个特殊的构造函数,用于初始化一个对象作为另一个对象的副本。下面是代码的逐行解释和为什么要这么做的原因:

  1. Person(const Person& p):这是拷贝构造函数的声明。它接受一个Person类型的引用作为参数,这个引用是对另一个Person对象的引用。
  2. this->name = new char[strlen(p.name) + 1];:这行代码使用new操作符动态分配内存,以便存储拷贝对象的名字。strlen(p.name)计算传入对象p的名字长度,+ 1是为了存储字符串的结束字符\0
  3. strcpy(this->name, p.name);:使用strcpy函数将传入对象p的名字复制到新分配的内存中。strcpy是一个C语言标准库函数,用于复制C风格的字符串。
  4. this->age = p.age;:将传入对象p的年龄直接赋值给当前对象的age成员。

拷贝构造函数的必要性:

  • 深拷贝:当类的成员变量包括指针指向动态分配的内存时(如本例中的name),拷贝构造函数需要进行深拷贝,即分配新的内存并复制内容,以避免两个对象共享同一块内存。如果不这样做,原始对象的销毁将导致内存泄漏。
  • 对象复制:在某些情况下,需要复制对象,例如函数按值传递对象、返回对象或将对象作为参数传递给函数时。拷贝构造函数允许这些操作安全地进行,而不会意外地修改原始对象。
  • 默认拷贝构造函数:如果没有显式定义拷贝构造函数,编译器会生成一个默认的拷贝构造函数,它通常只是简单地复制每个成员变量的值。但是,如果类中有指针成员,这可能导致浅拷贝问题,因此显式定义拷贝构造函数可以确保正确的深拷贝行为。
  • 一致性:通过自定义拷贝构造函数,可以确保对象的复制行为符合预期,特别是在涉及到资源管理(如内存、文件句柄等)时。
  • 异常安全:自定义的拷贝构造函数可以在复制过程中处理异常,确保即使在复制过程中发生错误,对象的状态也是一致的。

在本例中,拷贝构造函数确保了当创建一个Person对象的副本时,name字符串被正确地复制到新的内存位置,而age成员则直接赋值。这样可以保证新对象和原始对象在内存中是独立的,避免了潜在的内存问题。

  • 友元函数,把函数的声明放在类的里面同时在前面加上friend关键字,友元函数虽然不是类的成员函数,但是可以访问类的私有数据

  • 虚函数(Virtual Functions) :

  • 基类中的虚函数可以在派生类中被覆盖。当通过基类的指针或引用调用虚函数时,将调用对象实际类型的函数版本,而不是基类的版本

STL迭代器

  • set/map
  • isalpha
  • upper_bound
  • 导入map数据结构用insert
  • it = ma.find(x);//查找map中是否有x键的元素,有则返回该元素位置,没有则返回map.end()
  • make_pair('a', 1) 创建了一个 pair 对象,其包含字符 'a' 和整数 1,然后这个 pair 被插入到 myMap