基础学习笔记
1.c++标准库sstream的用法
<sstream>
定义了三个类:istringstream、ostringstream 和 stringstream,分别用来进行流的输入、输出和输入输出操作
**<sstream>
主要用来进行数据类型转换,由于<sstream>
使用 string 对象来代替字符数组(snprintf方式),就避免缓冲区溢出的危险;**而且,因为传入参数和目标对象的类型会被自动推导出来,所以不存在错误的格式化符的问题。简单说,相比c库的数据类型转换而言,<sstream>
更加安全、自动和直接。
数据类型转换例子:
#include <string>
#include <sstream>
#include <iostream>
#include <stdio.h>
using namespace std;
int main()
{
stringstream sstream;
string strResult;
int nValue = 1000;
// 将int类型的值放入输入流中
sstream << nValue;
// 从sstream中抽取前面插入的int类型的值,赋给string类型
sstream >> strResult;
cout << "[cout]strResult is: " << strResult << endl;
printf("[printf]strResult is: %s\n", strResult.c_str());
return 0;
}
多个字符串拼接例子:
本示例介绍在 stringstream 中存放多个字符串,实现多个字符串拼接的目的(其实完全可以使用 string 类实现),同时,介绍 stringstream 的清空方法。
#include <string>
#include <sstream>
#include <iostream>
using namespace std;
int main()
{
stringstream sstream;
// 将多个字符串放入 sstream 中
sstream << "first" << " " << "string,";
sstream << " second string";
cout << "strResult is: " << sstream.str() << endl;
// 清空 sstream
sstream.str("");
sstream << "third string";
cout << "After clear, strResult is: " << sstream.str() << endl;
return 0;
}
从上述代码执行结果能够知道:
- 可以使用 str() 方法,将 stringstream 类型转换为 string 类型;
- 可以将多个字符串放入 stringstream 中,实现字符串的拼接目的;
- 如果想清空 stringstream,必须使用
sstream.str("")
; 方式;clear() 方法适用于进行多次数据类型转换的场景。
stringstream的清空
清空 stringstream 有两种方法:clear() 方法以及 str(“”) 方法,这两种方法有不同的使用场景。str(“”) 方法的使用场景,在上面的示例中已经介绍了,这里介绍 clear() 方法的使用场景。示例代码(stringstream_test3.cpp)如下:
#include <sstream>
#include <iostream>
using namespace std;
int main()
{
stringstream sstream;
int first, second;
// 插入字符串
sstream << "456";
// 转换为int类型
sstream >> first;
cout << first << endl;
// 在进行多次类型转换前,必须先运行clear()
sstream.clear();
// 插入bool值
sstream << true;
// 转换为int类型
sstream >> second;
cout << second << endl;
return 0;
}
注意:
在本示例涉及的场景下(多次数据类型转换),必须使用 clear() 方法清空 stringstream,不使用 clear() 方法或使用 str(“”) 方法,都不能得到数据类型转换的正确结果。
2.setw()和setfill()用法
- 在C++中,setw(int n)用来控制输出间隔。
- setw()默认填充的内容为空格,可以setfill()配合使用设置其他字符填充。
例子:
cout<<'s'<<setw(8)<<'a'<<endl;
则在屏幕显示
s a
//s与a之间有7个空格,加上a就8个位置,setw()只对其后面紧跟的输出产生作用,如上例中,表示'a'共占8个位置,不足的用空格填充。若输入的内容超过setw()设置的长度,则按实际长度输出
我们在设置域宽和填充字符的时候要注意几点:
①设置域宽的时候应该填入整数,设置填充字符的时候应该填入字符。
②我们可以对一个要输出的内容同时设置域宽和 填充字符,但是设置好的属性仅对下一个输出的内容有效,之后的输出要再次设置。即 cout <<setw(2) <<a <<b;语句中域宽设置仅对a有效,对b无效。
③setw和setfill 被称为输出控制符,使用时需要在程序开头写上#include “iomanip.h”,否则无法使用。
3.static_cast用法
1.1 基础数据类型的转换
可以将一种基础数据类型转换为另一种基础数据类型。例如,将 double 转换为 int,或将 float 转换为 double 等。
double d = 5.5;
int i = static_cast<int>(d); // i = 5
1.2 指向派生类的指针或引用转换为指向基类的指针或引用
class Base {};
class Derived : public Base {};
Derived derivedObj;
Base* basePtr = static_cast<Base*>(&derivedObj);
1.3 指向基类的指针或引用转换为指向派生类的指针或引用
但这是不安全的,因为在转换过程中没有运行时检查。如果确实需要运行时检查,应使用 dynamic_cast。
Base* basePtr = new Base();
Derived* derivedPtr = static_cast<Derived*>(basePtr); // 不安全!
1.4 在有关联的类型之间进行转换
例如,转换枚举值为整数。
enum Color { RED, GREEN, BLUE };
int value = static_cast<int>(GREEN); // value = 1
限制:
-
不能用于转换不相关的指针或引用类型。例如,不能将 void* 转换为其他类型的指针,或反之。在这种情况下,应使用 reinterpret_cast。
-
不能用于移除或添加 const 限定符。在这种情况下,应使用 const_cast。
优点:
- 对于 C 风格的强制转换(如 (int)3.14),static_cast 更加明确和可读。
- 与 C 风格的强制转换相比,static_cast 只能执行明确允许的转换,这有助于避免一些错误。
注意:
使用 static_cast 时,需要确保转换在逻辑上是合理的,因为 static_cast 不执行运行时类型检查。如果对转换不确定,考虑使用其他类型的转换,如 dynamic_cast,或者重新评估设计,以避免需要转换
4.set中find的函数
Set.find()定义:
从set容器中查找值为x的元素,复杂度为O(log n):
若存在,返回一个迭代器,指向键值x;
若不存在,返回一个迭代器,指向set.end()。
查找set容器中是否有元素x:
set<int> test;
if(test.find(x)!=test.end());
{
···
}
输出find查找到元素的值:
set<int> test;
cout<<*test.find(x);
5.auto &详解
下面这段代码,localCharFreq是一个map<char, int> 类型,娜美为什么要用auto &p来表示每一个元素呢?
map<char, int> globalCharFreq;
for (int i = 0; i < strings.size(); ++i) {
map<char, int> localCharFreq;
for (char ch : strings[i]) {
localCharFreq[ch]++;
}
for (auto &p : localCharFreq) {
if (p.second >= n) {
if (i == 0) {
// 第一个字符串初始化计数
globalCharFreq[p.first] = 1;
} else if (globalCharFreq.find(p.first) != globalCharFreq.end()) {
// 后续字符串累加计数
globalCharFreq[p.first]++;
}
}
}
}
auto
的使用:auto
关键字使得编译器自动推导变量的类型。在这个上下文中,由于localCharFreq
是一个std::map<char, int>
,其元素类型为std::pair<const char, int>
。使用auto
可以避免手动书写繁长的类型声明,使代码更加简洁易读。- 为什么要加
&
:加上&
表示p
是对当前元素的引用,而不是其副本。这意味着循环体中对p
所做的更改将反映到localCharFreq
中的原始元素上。在这个场景里,虽然直接修改localCharFreq
中的元素并不是我们的目的(因为我们只是读取信息),使用引用同样是为了避免不必要的复制操作,提高效率。对于简单类型(如int
、char
)来说,使用引用与否的性能差异微乎其微,但对于复合类型(如std::pair<const char, int>
)来说,避免复制是一种好的实践,特别是当对象比较大或复制操作比较昂贵时。
总结起来,for (auto &p : localCharFreq)
的使用是为了通过引用直接访问localCharFreq
的每个元素,这样可以提高代码的效率和可读性。尽管在这个场合中修改元素并不适用,这种方法仍然被视为好的实践,以适应更广泛的使用场景。
6.Vector中的erase方法和unique函数
std::unique
是 C++ 标准库中定义在 <algorithm>
头文件中的一个函数模板,其主要用途是在一个序列中移除连续重复的元素。
函数原型如下:
template <class ForwardIterator>
ForwardIterator unique (ForwardIterator first, ForwardIterator last);
unique
函数通常与 sort
函数结合使用。首先对序列进行排序,以便所有相等的元素紧随其后,然后使用 unique
函数移除那些重复的元素。此时要注意,**unique
函数不实际删除任何元素,而是把要移除的元素移到容器末尾,并且返回一个指向"移除"元素区域开头的迭代器。**因此一般会将erase
方法和unique
函数结合使用,比如当移除vector中重复相邻的元素操作时,可以如下操作:
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 4};
auto last = std::unique(v.begin(), v.end());//此时返回的是“移除”元素区域的开头位置
v.erase(last, v.end());
for (const auto &i : v) {
std::cout << i << " ";
}
// Output: 1 2 3 4
}
7.stoul
函数详解
在 C++ 中,stoul
是一个标准库函数,用于将字符串转换成无符号长整型(unsigned long)。这个函数属于 <string>
头文件中定义的一系列转换函数之一,能够处理不同基数的数值字符串,如二进制、八进制、十进制和十六进制。
函数原型:
unsigned long stoul(const string& str, size_t* idx = 0, int base = 10);
unsigned long stoul(const wstring& str, size_t* idx = 0, int base = 10);
参数说明:
str
: 要转换的字符串,它包含了要转换的无符号长整型数。该字符串可以在数值前含有空格,函数会自动忽略这些空格。idx
: 一个可选的输出参数(通过指针传递),用于存储完成转换后的第一个不是数值字符的索引。如果不关心这个信息,可以默认传递0
。base
: 数值字符串的基数。默认值是10
,表示十进制,但可以设置为2
至36
之间的任何值,或者是特殊值0
。如果设定为0
,则函数会根据字符串的格式自动判断基数:如果字符串以 “0x” 或 “0X” 开头,则认为基数为16
(十六进制);如果字符串以 “0” 开头,则认为基数为8
(八进制);否则,默认为10
(十进制)。
返回值:
- 函数返回一个
unsigned long
类型的数值,它是从字符串中解析出的。
错误处理:
- 如果
str
不能转换成一个有效的数值,函数会抛出一个std::invalid_argument
异常。 - 如果解析的数值超出了
unsigned long
能表示的范围,函数会抛出一个std::out_of_range
异常。
示例:
#include <iostream>
#include <string>
int main() {
std::string number = "12345";
std::string hexNumber = "0x1ABCDE";
size_t idx = 0;
unsigned long n = std::stoul(number, &idx, 10);
unsigned long hexN = std::stoul(hexNumber, nullptr, 16); // Base is 16 for hexadecimal
std::cout << "Decimal: " << n << std::endl; // 输出: Decimal: 12345
std::cout << "Hexadecimal: " << hexN << std::endl; // 输出: Hexadecimal: 17777766
try {
std::string invalidNumber = "invalid";
std::stoul(invalidNumber); // This will throw std::invalid_argument
} catch (std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl; // 输出: Caught exception: invalid stoul argument
}
return 0;
}
在上面的示例中,我们可以看到如何将十进制和十六进制的字符串转换为 unsigned long
类型。同时,我们也看到了当提供了一个非法值的字符串时,stoul
如何通过抛出异常来处理错误。
8.C++函数中getline函数
在C++中,getline
函数是一个非常实用的函数,用于从输入流中读取一行。它通常用于从标准输入(即键盘)读取一行文本,或者从文件中读取一行。getline
函数有几个版本,但最常用的是与string
类一起使用的版本。
这里有一个例子来说明如何使用getline
函数:
#include <iostream>
#include <string> // 需要引入字符串库
using namespace std;
int main() {
string line;
// 从标准输入读取一行,存储到变量line中
cout << "请输入一些文字: ";
getline(cin, line);
// 输出读取的内容
cout << "你输入的是: " << line << endl;
return 0;
}
在上面的代码中,getline(cin, line);
这行代码从cin
(即标准输入,通常为键盘输入)读取一行文本,并将其存储在line
变量中。值得注意的是,这个函数会读取一行文本直到遇到换行符\n
为止,但换行符本身不会被存储在结果字符串中。
getline
函数具有高度的灵活性,尤其是在处理输入时不确定输入行的长度时。它可以读取任意长度的字符串,直到遇到结束的换行符,这使得它非常适合读取用户输入以及处理文本文件中的数据。
此外,getline
还有一个重载版本,允许我们指定除了标准的换行符\n
以外的分隔符。比如,如果你想按照逗号,
来分割文本行,你可以这样做:
getline(cin, line, ',');
这会从cin
读取文本直到遇到逗号,
为止,同样逗号不会被包括在line
字符串中。
总之,getline
函数是处理C++中字符串输入非常有用的函数,尤其是对于一行文本的读取。它简单、灵活并且易于使用。
9.emplace_back用法及和push_back区别
在 C++11
之后,vector
容器中添加了新的方法:emplace_back()
,和 push_back()
一样的是都是在容器末尾添加一个新的元素进去,不同的是 emplace_back()
在效率上相比较于 push_back()
有了一定的提升。
emplace_back()
函数在原理上比 push_back()
有了一定的改进,包括在内存优化方面和运行效率方面。内存优化主要体现在使用了就地构造(直接在容器内构造对象,不用拷贝一个复制品再使用)+强制类型转换的方法来实现,在运行效率方面,由于省去了拷贝构造过程,因此也有一定的提升。
在C++中,emplace_back
是一个非常有用的成员函数,它被定义在诸如std::vector
、std::deque
、std::list
等容器中。emplace_back
的功能是在容器的末尾直接构造一个新元素,与push_back
相比,emplace_back
可以减少不必要的对象拷贝或移动,提高性能。
基本用法
与push_back
相比,emplace_back
不直接将对象插入容器中,而是接收元素构造器的参数,并在容器末尾就地构造元素。这意味着可以避免临时对象的创建和复制(或移动),从而优化性能。
假设有以下简单结构体:
struct MyClass {
MyClass(int x, double y) : x(x), y(y) {
}
int x;
double y;
};
使用emplace_back
来在std::vector<MyClass>
中插入新元素的例子如下:
std::vector<MyClass> vec;
vec.emplace_back(10, 3.14); // 直接在容器末尾构造MyClass对象
对比使用push_back
:
vec.push_back(MyClass(10, 3.14)); // 构造临时MyClass对象,然后将它复制(或移动)到容器中
性能考虑
使用emplace_back
可以优化性能是因为它减少了对象拷贝或者移动的次数。当调用emplace_back
时,传递给emplace_back
的参数被直接用于在容器的存储空间中构造对象,而不需要额外的拷贝或移动操作。这一点在处理大型对象或资源管理密集型对象时特别有用。
语义差异
尽管emplace_back
在许多场景下都是更高效的选择,但它并不总是替代push_back
的最佳选择。根据对象的构造、拷贝和移动语义不同,选择不当可能会导致意想不到的副作用或性能问题。因此,合理选择push_back
和emplace_back
对于编写高效和可读的代码是非常重要的。
结论
在C++中,emplace_back
是一个强大的工具,它允许程序员在容器的末尾就地构造元素,从而减少不必要的对象拷贝或移动,优化性能。然而,正确的选择使用push_back
或emplace_back
需要对相关的构造、拷贝和移动语义有深刻的理解。
10.C++智能指针
0背景
使用指针,我们没有释放,就会造成内存泄露。但是我们使用普通对象却不会!
思考:如果我们分配的动态内存都交由有生命周期的对象来处理,那么在对象过期时,让它的析构函数删除指向的内存,这看似是一个 very nice 的方案?
智能指针就是通过这个原理来解决指针自动释放的问题!
-
C++98 提供了 auto_ptr 模板的解决方案
-
C++11 增加unique_ptr、shared_ptr 和weak_ptr
C++智能指针是C++11引入的一种特性,旨在自动管理动态分配的内存,从而避免内存泄漏和减少程序员出错的可能性。智能指针通过封装原始指针,在智能指针对象的生命周期结束时自动删除其所指向的对象。C++提供了三种智能指针:std::unique_ptr
、std::shared_ptr
和std::weak_ptr
。
- std::unique_ptr
std::unique_ptr
是一个独占所有权的智能指针。它拥有其所指向的对象,并且保证同一时间内只有一个unique_ptr
指向给定对象。当unique_ptr
被销毁(例如超出作用域)时,它所指向的对象也会被自动删除。这种智能指针非常适合管理那些不需要共享所有权的对象。
示例:
std::unique_ptr<int> ptr(new int(42));
// 当ptr离开作用域时,它所指向的int对象会被自动删除
- std::shared_ptr
std::shared_ptr
允许多个智能指针共享同一个对象的所有权。每个shared_ptr
都持有一个引用计数,当最后一个引用被销毁或重置时,所指向的对象才会被删除。这使得shared_ptr
非常适合管理需要被多个所有者共享的对象。
示例:
std::shared_ptr<int> ptr1(new int(