c++ primer plus 笔记 第十六章 string类和标准模板库

string类

string自动调整大小的功能:

string字符串是怎么占用内存空间的?

        前景:
        如果只给string字符串分配string字符串大小的空间,当一个string字符串附加到另一个string字符串上,这个string字符串是以占用相邻内存的方式进行扩容的,相邻的内存不够存放这个附加的字符串的时候,就需要将拼接起来的这两个string字符串迁移去一个新的内存空间来存放,如果多次这样的分配,会使得效率就很低。

        因此,c++会为一个string字符串提前预留空间,该空间比string字符串本身还大一些,当字符串的内存大于原本分配的这个内存的时候,程序会再分配一个原来大小两倍的内存块用来存储该string字符串,这样可以避免不断地分配内存。

        即c++会为String字符串分配更多的内存空间,而不是按需分配内存,这样可以减少因字符串扩大而导致需要迁移到新的内存空间的次数,从而提高效率。

        string提供了自己的内存管理功能,不用担心赋值给它的字符串是不是会超出它所能存储的范围,而c风格就要看一下所指向的内存空间放一大串字符串可能会出现越界的问题。

string的两个方法:

string str;
cout << str.capacity() << endl;//输出分配给str的内存空间的大小
str.reserve(50);//将分配给字符串的空间设置为50字节

如图:

//使用了虚方法
#include <iostream>
#include <string>
using namespace std;

int main()

{
	string str = "12345";
	cout << "str: " << str.capacity() << endl;
	string *pr = new string("12345");
	cout << "*pr: " << pr->capacity() << endl;
	return 0;
}

由图可知,不管是用直接用构造函数创建的对象还是new间接创建的对象(new后面加类名,那么就会调用构造函数来创建对象),他们所分配的内存空间都一样,且比原来string字符串本身大一些。

        举个栗子:

        一个抽屉里面原来放着a,现在想把b也放进一个同一个抽屉里面,如果放不下了怎么办,当然是找一个能放的下a,b的抽屉来存放a,b,这样拿来拿去也很麻烦,我们还可以每次放东西的时候都预留出一些空间,这样下次再放东西的东西就不至于马上就满了或者直接放不下,就是这样的思想。

智能指针模板类

前景:

        我们一般使用动态内存分配的时候,可能会忘记delete而导致开辟了的内存一直被占用,而这个指针消失后(比如在函数中开辟的内存空间,函数结束后该指针就被销毁了),该指针所指向的内存空间就再也无法收回了,这种情况就叫做内存泄漏,内存泄漏可能会导致程序的运行速度变慢,甚至崩溃。

(局部变量pd指针消失,地址为10000的内存无法再回收)

        因此就有了智能指针,智能指针的作用就是能够自动帮你回收动态分配的内存,不再需要自己主动delete。

怎么使用智能指针?

        要想使用智能指针,则需要包含头文件#include<memory>

智能指针有哪几种?

1.auto_ptr

2.shared_ptr

3.unique_ptr

如何使用智能指针?

        我们知道智能指针是一个模板类,因此我们就需要使用模板类的方式创建智能指针

auto_ptr<> p(new );
//<>中填充智能指针要指向的类型,()中需要使用new来创建类型,new返回的指针作为智能指针的参数
//举例
auto_ptr<double > p1(new double);//智能指针p1指向了一个动态分配的double内存空间
shared_ptr<double > p2(new double);
unique_ptr<double > p3(new double);

使用智能指针的时候需要注意什么问题?

1. 智能指针只能指向使用了动态内存分配的类或类型,不能指向非堆内存

string str = "hello world";
auto_pt<string > p(&str);
/*
这样会导致在智能指针p过期以后,
调用智能指针的析构函数将指针所指向的对象使用delete
但是str并没有动态开辟内存,这将导致段错误
实际上就是智能指针不能指向非堆内存
*/

2. 不能将指针直接赋值给智能指针

原因是,每一个类型的智能指针中都有一个explicit声明的构造函数,这将禁止隐式调用构造函数来生成智能指针,比如:

//我们本意是将一个指针的值赋值给智能指针
//让智能指针指向这片内存空间
double *p1 = new double;
shared_ptr<double > p2;
p2 = p1;
//我们知道等号左右两边类型不相同,在没有重载赋值运算符的情况下
//p1可能会作为智能指针构造函数的参数,隐式转换成智能指针对象然后再赋值给p2
//相当于p2 = shared_ptr<double > (p1);
//但因为我们智能指针的构造函数声明explicit,因此不能生成智能指针对象
//则导致等号两边类型不同而报错

当我们使用指针的时候,可能还会引发什么问题?
        一个就是浅拷贝的问题,也就是多个对象指向同一片内存空间,多个对象被析构时,可能会对这片内存空间进行多次删除。

如:

class A
{
    private:
        char *name;
       public:
        A(const char *str)
        {
            name = new [strlen(str)+1];
            strcpy(name, s);
        }
        ~A
        {
            delete []name;
        }

}

int main(void)
{

    A a1("hello world!");
    A a2 = a1;//调用默认的复制构造函数,进行浅拷贝,此时a2的name和a1的name同指向同一片内存空间
    return 0;
}
//当程序运行结束的时候,对象a1和a2调用析构函数,对同一片内存空间进行回收两次,导致段错误

智能指针就可以很好的解决这个问题,原因是什么呢?

        上面提到的auto_ptr和unique_ptr提出了所有权的概念,对于特定的对象,只能有一个智能指针可以拥有它,并只有该智能指针可以调用析构函数来删除该对象。

那么auto_ptr和unique_ptr就没有什么不同吗?如果都一样为什么要定义两种智能指针模板?

auto_ptr已经在c++98就已经被摒弃了,因为auto_ptr存在指针悬挂的问题,而unique_ptr就解除了这个指针悬挂的问题,unique_ptr在所有权的问题上更加严格。还有一个问题就是auto_ptr调用析构函数删除所指向的内存的时候使用的是delete没有delete[]的实现,这样可能就会导致内存泄漏,这也是摒弃auto_ptr的原因之一。

auto_ptr<int > p(new int [10]);//这将可能导致内存泄漏

什么是指针悬挂?怎么会有的指针悬挂?

我们知道auto_ptr和unique_ptr拥有所有权的概念,一个对象只能被一个智能指针拥有,而所有权是可以传递的,如果一个智能指针a将对象的所有权让给另一个智能指针b,那么指针a就悬挂了,就像一个空指针,那么如果再对这个智能指针进行操作就可能会给程序带来危险。

auto_ptr<double > p1(new double);
auto_ptr<double > p2 = p1;//p1将所有权传递给p2,此时p1就变成了一个悬挂的指针

在unique_ptr中,悬挂指针是不允许存在的,编译器会报错错误,而auto_ptr则不会。怎么区分什么样的智能指针赋值够能存在,什么样的智能智能赋值后不能存在,实际上unique_ptr是不允许存在一个长期存在的悬挂智能指针,但允许存在临时右值智能指针。

unique_ptr<string > demo(const char *s)
{
    unique_ptr<string > temp(new string(s));
    return temp;
}

int main(void)
{
    unique_ptr<string > p1(new string("hello"));//创建一个指向字符串的智能指针;
    unique_ptr<string > p2;
    p2 = p1;//这将不被允许,因为智能指针p1交出所有权后仍存在在程序中;
    p2 = demo("world");//demo函数返回一个临时的智能指针对象,然后将所有权转交给p2
                       //这句话运行结束后临时的智能指针对象消失,这将被允许。
    return 0;
}

即在unique_ptr中,交出所有权后消失是允许的,交出所有权后仍然存在在程序中这是不被允许的,即程序中存在被遗弃的智能指针,有被操作的风险。

如果我就要将一个智能指针赋值给另一个智能指针呢?

那就得使用c++中的std::move函数,这将声明赋值后的智能指针没有被抛弃,后面还会重新指向其他对象。

如:
 


using namespace std;
unique_ptr<string > p1(new string("hello"));
unique_ptr<string > p2;
p2 = move(p1);
p1 = demo("world");//p1重获另一个对象的所有权,而不是一个被抛弃的智能指针

那么shared_ptr呢?

它与对象的关系和auto_ptr和unique_ptr与对象的关系不太一样,shared_ptr允许多个智能指针指向一个对象(这也是shared名字的由来),会对对象拥有的智能指针数进行基数,只有当计数器为0时,才会调用析构函数来删除该对象。

那么什么时候使用shared_ptr?什么时候使用unique_ptr和auto_ptr呢?

不使用auto_ptr,程序要使用多个指向一个对象的智能指针的时候使用shared_ptr,其他情况用unique_ptr。

标准模板库

 迭代器

什么是迭代器?

可以认为迭代器就是一个广义指针,指针怎么用,迭代器就怎么用,迭代器可以看作是容器的指针,可以适用各种容器 ,是为了编程实现泛型,而指针是没有泛型的。

介绍一个算法for_each

for_each在头文件algorithm中,作用:作用于一对迭代器之间(一个范围),然后对范围中的每一个元素作为参数来执行特定的函数或者函数对象,注意:第三个参数传入的是函数的指针或者函数对象,而不是调用函数,也就是说后面不用加()。

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

class functor
{
    public:
        void operator()(int n){cout << n << endl;}//一元函数;
};

void show(int n)
{
    cout << n << endl;
}

int main(void)
{
    vector<int >v;
    functor f;//定义一个函数对象;
    for(int i = 0; i < 10; i++)
        v.push_back(i);
    for_each(v.begin(), v.end(), f);//在迭代器begin到end(左闭右开)的范围内,将元素作为参数传入到f中,每个元素都调用一次f();
    for_each(v.begin(), v.end(), show);
/*
    for_each(v.begin(), v.end(), f());//错误的写法
    for_each(v.begin(), v.end(), show());//错误的写法

*/
    
    return 0;
}

什么是函数对象?

函数对象就是假装自己是函数的对象,本质是一个对象,但是对运算符"()"进行了重载,这样这个类的对象就可以在自己的后面使用()运算符,这样看起来就像一个函数一样。

class functor//函数对象,也叫做函数符(functor)
{

    public:
        //void operator();//不用参数就可以调用的函数符,叫做生成器
        //void operator(int n);//用一个参数可以调用的函数符,叫做一元函数
        //void operator(int n, double m);//用两个参数可以调用的函数符,叫做二元函数
        //bool operator(int n);//返回类型为bool且为一元函数的函数符叫做谓词
        //bool operator(int n, double m);//返回类型为bool且是二元函数的函数符叫做谓词
        
}

对于for_each来说,for_each的第三个参数适合使用一元函数作为函数对象,原因是每次只处理一个元素。

介绍容器中的一种方法

remove_if()

举个栗子

#include <list>
#include <iostream>
#include<algorithm>

using namespace std;

const int MAXN = 10;

class functor
{
    private:
        int num;
    public:
          functor(int n = 0) {num = n;}
          bool operator()(int n)//谓词(返回为bool类型的一元函数)
            {
                if(n >= num)
                    return true;
                else 
                    return false;
            }
};

void Show(int n)
{
    cout << n << endl; 
}

int main(void)
{
    int arr[MAXN] = {50, 100, 90, 180, 200, 60};
    list<int > lis(arr, arr+MAXN);
    functor f(100);
    lis.remove_if(f);//删除lis中大于100的元素,如果f返回true则remove该元素;
    for_each(lis.begin(), lis.end(), Show);
    //从开始到结束的每个元素,都作为参数传入到Show函数中,即每个元素都会执行一次Show

    return 0;
}

基于范围的for循环:

既然说到了for_each,那就说说基于范围的for循环,这是一种新语法可以用来替代传统的for循环。

举个例子:

#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int > vec{1, 2, 3, 4, 5};
	for(const auto &num : vec)//auto会根据vec容器中的元素的数据类型来决定自己的数据类型
	{
	    cout << num << endl;
	}

	return 0;
}

valarray和vector有什么区别?

1.

可以认为,valarray是一个拥有数学运算方法的数组,而vector是一个可动态变化长度的数组,vector可以使用push_back来自动调整大小。虽然valarray中有resize的成员方法可以重新设置valarray对象可以存放的元素个数,但是不是自动调整大小还得手动设置。
2.

valarray这个模板类主要用于数值运算,因此这个模板类中并没有定义插入,排序以及搜索的成员函数,而vector,list,set等容器可以提供插入和删除的成员函数,但也没有查找和排序的成员函数,还是得借助STL库中的如find或者sort来对容器的元素进行查找和排序。

3.

我们说过valarray模板类在数值运算中十分方便,比如我们希望在该模板类对象中的元素全部+2

#include <iostream>
#include <valarray>
#include <vector>
using namespace std;


int main(void)

{
	valarray<int > arr1(10);
	for(int i = 0; i < 10; i++)
		arr1[i] = i;
	cout << "arr1: " << endl;
	for(int i = 0; i < 10; i++)
		cout << arr1[i] << " ";
	cout << endl;
	
	//valarray<double > arr2 = arr1+2; 	
	arr1 += 2;
	cout << "now arr1: " << endl;
	for(int i = 0; i < 10; i++)
		cout << arr1[i] << " ";
	cout << endl;
	return 0;
}

而如果是想将vector模板类对象中的数据都+2,则比较麻烦

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    std::transform(vec.begin(), vec.end(), vec.begin(),
                   [](int num) { return num + 2; });
//其中[](int num) { return num + 2; }是lambda函数,后面第18章将会有介绍

    for(const auto& num : vec) {
        std::cout << num << ' ';
    }

    return 0;
}

可见,valarray对于数组(口语化说法)数据进行运算十分方便。

4. 在概念上valarray确实可以视为一种容器,因为它存储并管理一组数据的集合,但根据c++标准库的分类,valarray并不属于容器序列(如vector, list, set等)。

介绍一个可以作为参数的,很好用的模板类,initializer_list<>

        我们知道,一个函数可以接收的参数个数在定义的时候就已经限制了,比方说我们定义一个求和函数sum,可以求两个数的和,即

double sum(double n1, double n2)
{
    double temp;
    temp = n1 + n2;
    return temp;
}

如果我们想求三个值的和呢?难道我们也重新定义一个sum函数吗?

因此c++11提出了一个initializer_list的模板类,它可以一次性接收多个参数,两个也可以,三个也可以...然后将这些参数作为initializer_list的参数创建一个initializer_list模板类的对象,然后我们将这个对象可作为sum的参数,让sum操作这个对象,这样就可以实现sum只接收一个initializer_list对象,却可以操作多个传给initializer_list的数据,这样编程更加灵活。

如何使用initializer_list?
首先,我们知道c++11新增了一个通用的初始化语法,就是使用大花括号{},这将调用initializer_list的构造函数,将大花括号里面的值作为参数,传给该initialiazer_list构造函数来创建对象。

举个例子

#include <initializer>
#include <vector>
vector<int > v1{1, 2, 3, 4, 5};
vector<int > v1(initializer_list<int > (1, 2, 3, 4, 5) );

这两句话是等价的,因此sum也可以有下述的写法:

//#include <initializer_lsit>
#include <iostream>

using namespace std;
double sum(initializer_list<int > i);

int main(void)
{
	int num1 = sum({1, 2, 3});//接收任意参数
	cout << "num1: " << num1 << endl;
	int num2 = sum({4, 5, 6, 7, 8});
	cout << "num2: " << num2 << endl;
	return 0;
}

double sum(initializer_list<int > i)
{
    int temp = 0;
    for(const auto & num : i)//基于范围的for循环
    {    	
        temp += num;
    }
    return temp;
}

一个可以用作数组索引的类slice

slice可以接收三个参数,第一个参数表示下标从哪里开始,第二个参数表示下表递增或者递减多少次,第三个参数表示每次递增递减多少值,注意,该类只适用valarray模板类对象,对普通数组或vector都不适用。

比方说:
 

#include <iostream>
#include <valarray>

int main(void)
{
    std::valarray<int> arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    std::slice s(0, 2, 3); // start at index 0, size of 2, stride of 3
    arr[s] = 0; 

    for(auto &v: arr) // Check the array values
        std::cout << v << " ";
    std::cout << std::endl;
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/447983.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

并发容器介绍(二)

并发容器介绍&#xff08;二&#xff09; 文章目录 并发容器介绍&#xff08;二&#xff09;BlockingQueueBlockingQueue 简介ArrayBlockingQueueLinkedBlockingQueuePriorityBlockingQueue ConcurrentSkipListMap 文章来自Java Guide 用于学习如有侵权&#xff0c;立即删除 Bl…

大模型字典中加入特殊字符

大模型字典中加入特殊字符 在微调大模型的时候会遇到添加特殊字符&#xff0c;例如在微调多轮的数据的时候需要加入人和机器等特殊标识字符&#xff0c;如用这个特殊字符表示人&#xff0c;用这个特殊字符表示机器&#xff0c;从而实现了人机对话。一般在大模型中base字典中不…

二次供水无人值守解决方案

二次供水无人值守解决方案 二次供水系统存在一定的管理难题和技术瓶颈&#xff0c;如设备老化、维护不及时导致的水质安全隐患&#xff0c;以及如何实现高效运行和智能化管理等问题。在一些地区&#xff0c;特别是老旧小区或农村地区&#xff0c;二次供水设施建设和改造滞后&a…

【go语言开发】redis简单使用

本文主要介绍redis安装和使用。首先安装redis依赖库&#xff0c;这里是v8版本&#xff1b;然后连接redis&#xff0c;完成基本配置&#xff1b;最后测试封装的工具类 文章目录 安装redis依赖库连接redis和配置工具类封装代码测试 欢迎大家访问个人博客网址&#xff1a;https://…

初学Vue——Vue路由

0 什么是Vue路由 类似于Html中的超链接(<a>)一样&#xff0c;可以跳转页面的一种方式。 前端路由&#xff1a;URL中hash(#号之后的内容)与组件之间的对应关系&#xff0c;如下图&#xff1a; 当我们点击左侧导航栏时&#xff0c;浏览器的地址栏会发生变化&#xff0c;路…

hutool,真香!

大家好&#xff0c;我是苏三&#xff0c;又跟大家见面了。 前言 今天给大家介绍一个能够帮助大家提升开发效率的开源工具包&#xff1a;hutool。 Hutool是一个小而全的Java工具类库&#xff0c;通过静态方法封装&#xff0c;降低相关API的学习成本&#xff0c;提高工作效率&…

IOT的发展历程及其优势——青创智通

工业互联网-物联网-设备改造-IOT-青创智通 ​随着科技的不断发展&#xff0c;物联网&#xff08;IoT&#xff09;已经逐渐成为了我们生活中不可或缺的一部分。IoT是指通过互联网将各种物理设备连接起来&#xff0c;实现设备之间的数据交换和智能化控制。IoT的发展不仅改变了我们…

四管齐下 共建发展 | 七巧低代码助力零售行业打造一体化协同解决方案

行业背景 随着互联网和移动技术的普及&#xff0c;零售行业的销售渠道日趋多元化和融合化&#xff0c;传统线下渠道和新兴线上渠道相互竞争和协作&#xff0c;形成了新零售和全渠道的格局。快消品新零售模式下&#xff0c;企业需要通过数字化和智能化的手段&#xff0c;实现对…

Flask python 开发篇:项目布局

一、背景简介 Flask应用程序可以像单个文件一样简单。就像上一篇简单实现一个接口一样&#xff0c;所有的东西都在一个python文件内&#xff1b; 然而&#xff0c;当项目越来越大的时候&#xff0c;把所有代码放在单个文件中就有点不堪重负了。 Python 项目使用 包 来管理代码…

携手亚信安慧AntDB,在数智化浪潮中乘风破浪

随着大数据时代的到来&#xff0c;对数据库的需求愈发强烈。在这一背景下&#xff0c;国产数据库逐渐崭露头角&#xff0c;亚信安慧AntDB作为重要的代表产品之一正积极参与到激烈的市场竞争中。亚信安慧AntDB不仅追求技术的革新和突破&#xff0c;同时也致力于满足用户日益增长…

【Python】conda 命令报错解决:Example: conda --no-plugins install <package>

目录 报错效果&#xff1a;解决方法总结 欢迎关注 『Python』 系列&#xff0c;持续更新中 欢迎关注 『Python』 系列&#xff0c;持续更新中 报错效果&#xff1a; An unexpected error has occurred. Conda has prepared the above report. If you suspect this error is bei…

OD_2024_C卷_200分_9、园区参观路径【JAVA】【动态规划】

package odjava;import java.util.Scanner;public class 九_园区参观路径 {public static void main(String[] args) {Scanner sc new Scanner(System.in);int n sc.nextInt(); // 长 -> 行数int m sc.nextInt(); // 宽 -> 列数int[][] matrix new int[n][m]; // 地图…

HAproxy反向代理与负载均衡

目录 一、HAproxy介绍 1. 概述 2. 关于4/7层负载均衡 2.1 无负载均衡 2.1.1 图示 2.1.2 说明 2.2 四层负载均衡 2.2.1 图示 2.2.2 说明 2.3 七层负载 2.3.1 图示 2.3.2 说明 3. 特性 4. HAProxy负载均衡常见策略 5. 处理模式 二、HAproxy安装 1. yum安装 2. 第…

算法---双指针练习-7(三数之和)

三数之和 1. 题目解析2. 讲解算法原理3. 编写代码 1. 题目解析 题目地址&#xff1a;三数之和 2. 讲解算法原理 首先对输入的数组进行排序&#xff0c;以便后续使用双指针法。初始化一个空的二维向量 ret&#xff0c;用于存储结果。使用一个循环遍历数组中的每个元素&#xff…

Spark性能优化指南——高级篇

调优概述 有的时候&#xff0c;我们可能会遇到大数据计算中一个最棘手的问题——数据倾斜&#xff0c;此时Spark作业的性能会比期望差很多。数据倾斜调优&#xff0c;就是使用各种技术方案解决不同类型的数据倾斜问题&#xff0c;以保证Spark作业的性能。 数据倾斜发生时的现…

【Idea】八种Debug模式介绍

1.行断点 在对应的代码行左侧边栏点击鼠标左键&#xff0c;会出现一个红色圆圈&#xff0c;以debug模式执行时当代码运行到此处则会停止&#xff0c;并可以查询相关上下文参数 2.方法断点 在方法左侧点击创建断点,在方法进入时会停止&#xff0c;同时可以右键断点&#xff0c;…

Jenkins Pipeline实现Golang项目的CI/CD

Jenkins Pipeline实现Golang项目的CI/CD 背景 最近新增了一个Golang实现的项目&#xff0c;需要接入到现有的流水线架构中。 流程图 这边流程和之前我写过的一篇《基于Jenkins实现的CI/CD方案》差不多&#xff0c;不一样的是构建现在是手动触发的&#xff0c;没有配置webho…

在 Python 中从键盘读取用户输入

文章目录 如何在 Python 中从键盘读取用户输入input 函数使用input读取键盘输入使用input读取特定类型的数据处理错误从用户输入中读取多个值 getpass 模块使用 PyInputPlus 自动执行用户输入评估总结 如何在 Python 中从键盘读取用户输入 原文《How to Read User Input From t…

Elixir and Pylons 中多态继承和自关联关系的创建

我们知道&#xff0c;在Elixir和Pylons中&#xff0c;多态继承和自关联关系是两个独立的概念&#xff0c;分别用于处理不同的情况。而在Pylons中&#xff0c;多态继承通常由SQLAlchemy提供的 polymorphic 关系来实现。下面分别介绍在Elixir和Pylons中如何创建多态继承和自关联关…

vue之性能优化

1.路由懒加载 所谓路由懒加载&#xff0c;其实就是路由通过import动态引入&#xff0c;而不是在文件最上面一个个全部引入&#xff0c;因为JS执行的时候会优先执行引入的文件&#xff0c;如果一次性引入过多&#xff0c;则会增加处理时长。 2.图片懒加载 图片在网页加载过程…