建设永久网站,网站开发的岗位与分工,电商网站建设方案,电子商务网站登录目录
string的收尾
拷贝构造的现代写法#xff1a;
浅拷贝#xff1a; 拷贝构造的现代写法#xff1a;
swap函数#xff1a;
内置类型有拷贝构造和赋值重载吗#xff1f;
完善拷贝构造的现代写法#xff1a;
赋值重载的现代写法#xff1a; 更精简的现代写法
浅拷贝 拷贝构造的现代写法
swap函数
内置类型有拷贝构造和赋值重载吗
完善拷贝构造的现代写法
赋值重载的现代写法 更精简的现代写法
初学vector
插入数据
访问vector的数据。 理解vector
reserve
resize
vector如何进行扩容
算法题目
题目1 题目2 string的收尾
拷贝构造的现代写法
浅拷贝 其中s1的_str指向字符串hello world用s1拷贝构造s2
s2(s1);
当我们发生浅拷贝时我们的s1的_str和s2的_str指向同一块空间
如图所示 这就是所谓的浅拷贝。 浅拷贝的定义 拷贝对象和源对象指向的空间是相同的。 浅拷贝造成的问题 1当s1调用完毕析构函数后s2指向的空间也被析构了s2也调用自己的析构函数所以会导致一块空间被析构两次的问题。 2对s1或s2其中一个对象的修改会导致另一个对象也发生变化。 拷贝构造的现代写法
string(const strings){string tmp(s._str);swap(_str, tmp._str);swap(_size, tmp._size);swap(_capacity, tmp._capacity);}
问题1为什么这里的参数需要加上const
答我们函数的目的是为了用s进行拷贝构造一个新的对象所以我们最好不要对s本身进行修改所以我们加上const表示s是只读的。
我们对代码进行理解 string tmp(s._str); 我们可以通过构造函数实现 我们首先调用构造函数 构造完毕如图所示 swap(_str, tmp._str);
swap(_size, tmp._size);
swap(_capacity, tmp._capacity); 接下来调用swap函数 表示交换两个目标的值我们把tmp的全部都与this指针指向的都西昂进行交换如图所示 那么我们的s2指向的内容就是hello world了并且我们的s2和s1的_str并不相同我们实现了深拷贝。 我们进行实验
void test_string1(){string s1(hello world);string s2(s1);cout s2.c_str() endl;} 我们完成了拷贝构造的深拷贝。
问题1如图 我们tmp现在指向的是原本属于s2的那部分空间但是我们的s2并没有进行初始化所以我们的s2指向的是随机值我们的tmp是一个临时对象函数栈帧调用完毕就会调用析构函数我们delete函数对于野指针会报错。 我们如何预防这种问题呢
答我们可以使用初始化列表先把s2置为空指针如代码所示
string(const strings):_str(nullptr),_size(0),_capacity(0){string tmp(s._str);swap(_str, tmp._str);swap(_size, tmp._size);swap(_capacity, tmp._capacity);} delete对于空指针不做处理所以不会报错。
swap函数
s1.swap(s2);
swap(s1, s2);
这两句代码有什么区别吗
我们首先要清楚这里调用了不同的swap函数第一个调用的是string的接口swap函数 第二个调用的是标准库里面的swap函数 标准库的swap函数是用类摸板通过调用三次拷贝构造来实现的。
那种更好更高效呢
答对于string的调用无疑是第一种更好更高效因为我们的第二种调用了三次拷贝构造调用三次string的拷贝构造效率太低。
但是第一种是string的接口我们的目的是模拟实现string所以我们也要对string的swap函数进行模拟实现。
void swap(strings){swap(_str, s._str);swap(_size, s._size);swap(_capacity, s._capacity);}
这样写对吗
不对原因如下
因为swap是在函数内部首先在局部域找swap函数找到了我们自己定义的swap函数但是我们自己定义的swap函数只有一个参数而我们调用swap函数却有两个参数所以会报错我们可以这样修改。
void swap(strings){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}
我们使用域作用限定符表示我们调用的swap是标准库里面的swap函数。 但是我们的标准库里面的swap函数需要调用三次拷贝构造这个该怎么处理呢
我们不需要考虑这个问题因为我们swap调用的参数在这里全部是内置类型对内置类型的拷贝构造不会造成效率的损失。 内置类型有拷贝构造和赋值重载吗 答原则上并没有但是为了匹配摸板不得不有但是我们调用内置类型的拷贝构造和赋值重载和内置类型的初始化和赋值没有区别。 完善拷贝构造的现代写法
string(const strings):_str(nullptr),_size(0),_capacity(0){string tmp(s._str);swap(tmp);}
我们可以把原来的三个swap函数写成一个。
这个swap的本质是调用我们自己实现的string类里面的swap函数其实等价于
this-swap(tmp);
赋值重载的现代写法
string s1(hello world);
string s3(hello bit);
s1 s3;
stringoperator(const strings){if (this ! s){string tmp(s);swap(tmp);}return *this;}
我们调用赋值重载函数需要返回*this所以我们要用string来接收。
我们进行判断如果this和s的地址不相同表示我们的s1和s3指向的不是同一块空间我们调用赋值函数首先调用拷贝构造如图所示 我们直接把tmp和s1上的内容进行交换即可原因是我们实现的拷贝构造是深拷贝所以tmp和s3的内容相同但是指向的空间是不同的然后我们再把s1和tmp进行交换交换之后的结果如图所示 更精简的现代写法
stringoperator(string s){swap(s);return *this;} 我们的参数不传引用既然不传引用那么s就是s3的拷贝s和s3指向的空间并不相同然后我们调用swap函数即可。
初学vector vector是一个可以更改元素的数组vector中可以存一种任意类型的数据。
插入数据
#includeiostream
#includevector
using namespace std;
void test_vector1()
{vectorint v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);cout endl;
}
int main()
{test_vector1();return 0;
} 我们进行调试 这就是所谓的插入函数。
访问vector的数据。
方法1通过[]来进行访问
vectorint v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (size_t i 0; i v.size(); i){cout v[i] ;}
cout endl;
这种方法并不是万能的string和vector中都有[]但是list中没有[]
方法2通过迭代器访问
vectorint::iterator it v.begin();while (it ! v.end()){cout *it ;it;}cout endl;
这种方法是万能的因为每一个接口都有迭代器。
方法3通过范围for访问
for (auto ch : v){cout ch ;}cout endl;
范围for的底层其实是迭代器的另一种表达所以没有迭代器也就没有范围for 理解vector 一个是内容是字符类型的vector一个是string类。
他们是不同的因为str的本质是字符串而vstr的本质是数组字符串的末尾一定是\0而数组没有这个要求。
string可以比较以ascll码的形式进行比较但是vector最好不要比较原因是vector中不仅可能有内置类型也有可能有自定义类型的内容。
reserve reserve函数的作用是修改容量。
我们思考一个问题reserve函数可以缩容吗?
答并不能我们进行证明
vectorint v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.reserve(10);cout v.capacity() endl;v.reserve(4);cout v.capacity() endl; reserve函数并不能够缩容原因是因为缩容的代价太大时间效率是最重要的我们采用以空间换时间的思想不对空间进行缩容。
resize 这里的size_type和value_type是什么意思
size_type其实就是无符号整型的意思。
value_type就是vector中存放的数据类型。 这是什么意思
这里的value_type我们可以等价为T().当我们这样写代码时
v.resize(8); 我们没有填写第二个参数第二个参数的缺省值是value_type()。
这个value_type()本质是T()假如我们的vector中的数据类型是string类型那这个T()就是string类型的匿名对象假如我们的vector中的数据类型是int类型那这个T()就是int类的匿名对象也就是0加入我们的vector中的数据类型是一个指针那么这个T()就是指针的匿名对象也就是空指针。
我们可以这样吗 不行对于整形 浮点型我们可以把其看作0对于指针我们可以把其看作空指针但是对于自定义类型呢自定义类型并不能用0初始化。 所以不能这样写。
vector如何进行扩容
void TestVqectorExpand()
{size_t sz;vectorint v;sz v.capacity();cout making v grow:\n;for (int i 0; i 100; i){v.push_back(i);if (sz ! v.capacity()){sz v.capacity();cout capacity changed: sz \n;}}
}
这串代码可以检测我们的vector是如何进行扩容的。 我们可以发现扩容大概是每次扩容1.5倍这样的好处是什么
答倍数过大时容易造成空间浪费倍数过小容易导致频繁扩容。
扩容是有消耗的为了防止扩容我们可以使用reserve提前开好空间。 算法题目
题目1
136. 只出现一次的数字 - 力扣Leetcode 我们需要了解^,异或表示相同为0不同的话为1针对的是二进位制 这个数组中只出现一次的元素是4我们如何找到4呢我们可以进行异或 我们知道相同的数字异或的结果为0并且异或满足结合律 我们可以把相同的数字放在一起 1异或1的结果为02异或2的结果为00异或0的结果为04异或0的结果为4. 异或a^b我们要进行判加入a和b都为0时结果为0加入a或b其中一个为0那结果就是另一个值当a和b都不同且不为0我们写出他们两个对应的二进位制相同的二进位制的结果为0不同的二进位制的结果为1求出的二进位制对应的数字就是我们要的结果。 并且异或满足结合律一组数组中加入有相同的数字这些数字我们可以把他们放在一起进行计算计算的结果也是0。 所以我们可以这样写代码
class Solution {
public:int singleNumber(vectorint nums) {int ret0;for(auto ch:nums){ret^ch;}return ret;}
}; 题目2
118. 杨辉三角 - 力扣Leetcode 假如我们用c语言写的话 我们需要先malloc一个指针数组 我们需要再在指针数组上进行malloc申请空间。 我们在释放的时候也需要先把一维数组释放掉再释放二维数组。
可以发现c语言写这道题目操作难度太大我们可以用c来写。 这里表示二维数组generate首先是一个vectorvector中的元素的类型是vectorint class Solution {
public:vectorvectorint generate(int numRows) {vectorvectorint vv;vv.resize(numRows);for(size_t i0;ivv.size(),i){vv[i].resize(i1);vv[i][0]vv[i][vv[i].size()-1]1;}for(size_t i0;ivv.size();i){for(size_t j0;jvv[i].size();j){vv[i][j]vv[i-1][j]vv[i-1][j-1];}}return vv;}
}; 我们对代码进行逐步分析 我们首先创建一个二维数组vector接下来调用resize函数进行初始化我们的vv的元素个数为numRows 我们调用resize函数不传第二个参数那第二个参数就是vectorint类型的匿名对象也就是0.
我们看杨辉三角有什么特点 我们可以发现杨辉三角的每一行的首元素和尾元素都为1并且每一行的元素个数都比前一行的元素个数多1个。 我们遍历数组的每一个元素首先在每一个元素位置开辟对应的元素个数然后把每一行的首元素和尾元素都置为空。 接下来我们只需要实现这些中间元素即可。 我们发现中间的每一个元素都为这个元素正上方的元素和正上方的左面一个单位的元素之和。
接下来我们返回vv即可