今天在朋友圈看到别人发的一套C++面经,特意思考了一下
以下是一些可以提高在C++中向vector中插入大量数字性能的技巧:
vector<int> v;
int x = 123;
v.push_back(std::move(x));
vector<int> v;
vector<int> data(1000);
v.insert(v.end(),data.begin(),data.end());
std::vector<std::string> v;
std::string str = "hello";
v.insert(v.end(), std::make_move_iterator(std::begin(str)), std::make_move_iterator(std::end(str)));
以下是一些可以提高在C++中从vector中删除偶数位置的数字的性能的技巧:
std::vector<int> v{...}; // 假设有10000个数字
v.erase(std::remove_if(v.begin(), v.end(), [](const auto& x){ return &x - &v[0] % 2 == 0; }), v.end());
std::vector<int> v{...}; // 假设有10000个数字
v.reserve(v.size() / 2);
std::vector<int> v{...}; // 假设有10000个数字
v.erase(std::remove_if(v.begin(), v.end(), [i = 0](const auto&){ return i++ % 2 == 0; }), v.end());
该代码使用了lambda表达式作为std::remove_if算法的谓词,其中[i = 0]表示定义了一个名为i的变量,并初始化为0。i++ % 2 == 0表示先取i的值,然后将i加1,再判断该值是否为偶数。使用迭代器递增器可以避免使用索引操作符的性能开销。
在这句代码中,const auto&是一个lambda表达式的参数,它代表了一个引用类型的元素。lambda表达式是一个匿名函数,它可以接受一个或多个参数,这些参数可以是值类型、引用类型或者指针类型。
在这个lambda表达式中,使用const auto&作为参数,表示接受一个任意类型的常量引用。在std::remove_if算法中,该lambda表达式作为谓词被调用,每次调用时会传入vector中的一个元素,并对该元素进行判断。由于lambda表达式中的参数是一个常量引用,所以在对该元素进行判断时不会改变原始vector中的元素。
在该lambda表达式中,i++ % 2 == 0表示先取i的值,然后将i加1,再判断该值是否为偶数。由于在lambda表达式中定义了一个变量i,所以可以通过该变量来判断元素在vector中的位置是否为偶数位置。
malloc()和delete是两个不同的内存分配和释放函数,它们具有不同的行为和约定。使用malloc()分配的内存需要使用free()函数进行释放,而使用new运算符分配的内存需要使用delete运算符进行释放。因此,如果使用delete释放使用malloc()分配的内存,会导致内存错误和未定义的行为。
使用delete释放使用malloc()分配的内存,可能会导致以下问题:
内存泄漏:如果使用delete释放使用malloc()分配的内存,将无法正确释放内存,导致内存泄漏。
崩溃或异常:使用delete释放使用malloc()分配的内存,可能会导致程序崩溃或抛出异常,因为delete假设它的参数是通过new分配的内存。
不正确的内存释放:使用delete释放使用malloc()分配的内存,可能会释放掉不属于该内存块的内存,或者释放掉一个已经被释放的内存块,导致不正确的内存释放。
weak_ptr是C++11中引入的一种智能指针类型,用于解决shared_ptr循环引用导致的内存泄漏问题。在使用shared_ptr进行循环引用时,如果存在两个或多个shared_ptr相互引用,那么它们之间的引用计数永远不会达到0,导致分配的内存永远无法被释放。而weak_ptr则可以作为一个非拥有者指针,指向shared_ptr所管理的对象,但不会增加引用计数,因此不会导致循环引用和内存泄漏问题。
lock()函数是weak_ptr提供的一个成员函数,用于返回一个指向weak_ptr所管理对象的shared_ptr对象。lock()函数首先检查weak_ptr所指向的对象是否还存在,如果存在,则返回一个指向该对象的shared_ptr对象;否则,返回一个空的shared_ptr对象。使用lock()函数获得shared_ptr对象后,可以使用该对象来访问weak_ptr所管理的对象。需要注意的是,在使用lock()函数获得shared_ptr对象之前,需要检查lock()返回的shared_ptr对象是否为空,如果为空则表示weak_ptr所指向的对象已经被销毁了。
返回的shared_ptr对象可以直接使用,但需要注意其生命周期。shared_ptr对象在所有指向该对象的shared_ptr对象都被销毁后才会自动销毁,因此如果需要使用shared_ptr对象,需要确保该对象的生命周期符合预期,以避免内存泄漏等问题。另外,由于weak_ptr对象只是一个非拥有者指针,它并不拥有shared_ptr所管理的对象,因此需要确保在使用lock()函数返回的shared_ptr对象之前,shared_ptr所管理的对象仍然存在,否则会出现未定义的行为。
左值引用和右值引用都是C++中的引用类型,用于表示一个对象的别名。其中左值引用表示对一个左值对象的引用,而右值引用表示对一个右值对象(包括临时对象和将要销毁的对象)的引用。
左值引用的声明形式为T&,表示对类型为T的左值对象的引用;而右值引用的声明形式为T&&,表示对类型为T的右值对象的引用。例如,以下代码声明了一个左值引用和一个右值引用:
int x = 123;
int& lref = x; // 左值引用
int&& rref = 123; // 右值引用
在C++11之前,如果没有编写移动构造函数,当一个对象被移动构造时,实际上是调用了该对象的拷贝构造函数。因此,如果一个类没有显式定义移动构造函数,但是有拷贝构造函数,当使用右值引用移动构造该对象时,实际上是调用该类的拷贝构造函数。
在C++11及之后的版本中,如果一个类定义了移动构造函数,那么当使用右值引用移动构造该对象时,实际上是调用该类的移动构造函数。移动构造函数是一种特殊的构造函数,用于将一个对象的资源(如堆内存、文件句柄等)从一个对象转移到另一个对象,而不进行数据的复制。使用移动构造函数可以避免拷贝大量数据的开销,提高程序的性能。
需要注意的是,如果一个类既定义了拷贝构造函数,又定义了移动构造函数,那么在使用右值引用移动构造该对象时,会优先调用移动构造函数。如果没有定义移动构造函数,但是定义了移动赋值运算符,那么在使用右值引用进行赋值操作时,会调用移动赋值运算符。如果同时没有定义移动构造函数和移动赋值运算符,但是定义了析构函数,那么在对象销毁时,会使用拷贝构造函数进行拷贝构造。如果以上所有函数都没有定义,那么编译器会生成默认的函数实现。
class MyClass {
public:
// 移动构造函数
MyClass(MyClass&& other) noexcept {
// 将other对象的资源移动到当前对象
data_ = other.data_;
size_ = other.size_;
// 将other对象的资源释放
other.data_ = nullptr;
other.size_ = 0;
}
// 其他成员函数
// ...
private:
int* data_;
size_t size_;
};
该移动构造函数使用右值引用作为参数类型,并且使用了noexcept关键字,表示该函数不会抛出异常。在移动构造函数中,首先将另一个对象的资源移动到当前对象,然后将另一个对象的资源释放,以避免资源的重复释放。
在该例子中,假设data_是一个指向堆内存的指针,size_表示堆内存的大小。在移动构造函数中,将另一个对象的data_指针和size_成员移动到当前对象,然后将另一个对象的data_指针置为nullptr,size_成员置为0,以表示另一个对象的资源已经被移动到当前对象。
移动构造函数可以在类中定义,也可以在类外定义。如果在类外定义,需要在函数名前添加类名和作用域解析符::。需要注意的是,移动构造函数的参数应该是一个非const右值引用,并且应该使用noexcept关键字标记,以便在移动操作时可以获得更好的性能和安全性。
移动构造函数的参数为什么要是一个非const右值引用呢? 这是因为移动构造函数的参数为非const右值引用是为了表示该参数是一个将要销毁的对象(右值对象),并且该对象的资源可以被移动到另一个对象中。非const右值引用参数的特点是不能被赋值、取地址和修改值,因此移动构造函数可以对该参数进行资源的“窃取”,而不必担心该对象被修改或复制。此外,由于移动构造函数可以修改参数对象的状态,因此该参数不能是const类型的,否则会导致编译错误。
std::move是C++11引入的一个函数模板,用于将一个左值转换为右值引用,以便在移动构造函数和移动赋值运算符中使用。std::move本质上只是将一个左值强制转换为右值引用,它并不会实际地移动对象或改变对象的状态。
在正常情况下,使用std::move可以将一个左值对象的资源(如堆内存、文件句柄等)移动到一个新的对象中,从而避免了拷贝大量数据的开销,提高了程序的性能。使用std::move时需要注意以下几点:
在map和unordered_map容器中实现大小写不敏感,可以通过自定义比较函数来实现。具体来说,可以重载std::map和std::unordered_map容器中的Compare类型,定义一个自己的比较函数对象,并将其作为容器的第三个模板参数传递进去。比较函数对象需要重载operator()运算符,对于字符串类型的键,可以在比较之前将所有字符转换成小写或大写字母,然后再进行比较。
以下是一个示例代码:
#include
#include
#include
#include
#include
struct CaseInsensitiveLess
{
bool operator()(const std::string& lhs, const std::string& rhs) const
{
std::string lstr(lhs);
std::string rstr(rhs);
std::transform(lstr.begin(), lstr.end(), lstr.begin(), ::tolower);
//transform函数: transform是一个通用算法,用于对指定区间内的元素进行转换,并将结果存储在另一个区间中。transform算法接受两个输入迭代器和一个输出迭代器作为参数,以及一个一元或二元操作函数对象(可通过函数指针、函数对象、Lambda表达式等形式指定),将指定区间内的元素依次传入操作函数进行转换,然后将转换后的结果存储到输出迭代器指向的位置。
std::transform(rstr.begin(), rstr.end(), rstr.begin(), ::tolower);
return lstr < rstr;
}
};
int main()
{
std::map<std::string, int, CaseInsensitiveLess> m;
m["a"] = 1;
m["B"] = 2;
m["c"] = 3;
std::cout << m["A"] << std::endl; // 输出 1
std::cout << m["b"] << std::endl; // 输出 2
std::unordered_map<std::string, int, std::hash<std::string>, CaseInsensitiveLess> um;
um["a"] = 1;
um["B"] = 2;
um["c"] = 3;
std::cout << um["A"] << std::endl; // 输出 1
std::cout << um["b"] << std::endl; // 输出 2
return 0;
}