C++学习笔记(1)

C++学习笔记(1)

《21天学通C++(第8版)》Siddhartha Rao著

auto

  1. auto自动推断类型(C++11新增)

    //使用auto必须进行初始化
    auto largeNumber = 25000000000000; //编译器根据初始值来确定合适的值,这里应该为long long
    
  2. auto迭代

    int a[] = {1,2,3,4,5};
    for(auto temp : a)
    {
       cout<<temp<<endl;
    }
    
    //auto& 可以修改数组a的值
    for(auto& temp : a)
    {
        temp++;
        cout<<temp<<endl;
    }
    

const; constexpr

  1. 如果变量的值不应该被改变,就应该将其声明为常量。

    const double PI = 3.1415926;
    
    constexpr double GetPi(){ return 22.0/7;} //使用constexpr声明常量表达式
    
    enum CardinalDirections
    {
        North,
        South,
        East,
        West
    };
    
    //不要使用#define定义常量,该方法已被摒弃
    

不要使用#define定义常量,该方法已被摒弃。

动态数组

std::vector

C++字符串

  1. 避免C风格字符串:以’\0’作为终止空字符。
  2. 使用std:string,包含#include<string>

内联函数

  1. 函数非常简单,且需要降低开销的时候,可以声明为内联函数。

lambda函数(C++11新增)

指针

  1. 指针是存储内存地址的变量。

    int *p = NULL;//初始化,未初始化的指针包含的值是垃圾值(随机的),可能导致问题(非法访问)。
    
  2. 使用引用运算符&获取变量的地址。

  3. 解除引用运算符*访问指向地址所存储的值。

  4. const修饰指针

    • 修饰 指针包含的地址为常量,无法修改,但可修改指针指向的数据。

      int daysInMonth = 30;
      int* const pdaysMonth = &daysInMonth;
      *pDaysMonth = 31;//OK!
      int daysInLUnarMonth = 28;
      
      pDaysMonth = &daysInLUnarMonth;//Not OK!
      
    • 修饰 指针指向的数据为常量,不能修改,但可以修改指针包含的地址。

      int daysInMonth = 30;
      const int* pdaysMonth = &daysInMonth;
      *pDaysMonth = 31;//Not OK!
      int daysInLUnarMonth = 28;
      
      pDaysMonth = &daysInLUnarMonth;//OK!
      
      int* newMonth = pDaysMonth;//Not OK!
      
    • 修饰 指针包含的地址以及它指向的值都是常量,不能修改。

      int daysInMonth = 30;
      const int* const pdaysMonth = &daysInMonth;
      *pDaysMonth = 31;//Not OK!
      int daysInLUnarMonth = 28;
      pDaysMonth = &daysInLUnarMonth;//Not OK!
      
  5. 数组可赋给指针。

  6. 使用指针常见错误:

    • 内存泄漏
    • 指针指向无用的内存单元:未初始化

new(std::nothrow)

  1. new(std::nothrow) 顾名思义,即不抛出异常,new一个对象失败时,默认设置该对象为NULL,这样可以方便的通过if(p == NULL) 来判断new操作是否成功。
  2. 普通的new操作,如果分配内存失败则会抛出异常,虽然后面一般也会写上if(p == NULL) 但是实际上是自欺欺人,因为如果分配成功,p肯定不为NULL;而如果分配失败,则程序会抛出异常,if语句根本执行不到。
  3. 因此,建议在c++代码中,凡是涉及到new操作,都采用new(std::nothrow),然后if(p==NULL)的方式进行判断
#include <iostream>
using namespace std;

int main()
{
	int* points = new(nothrow) int[10];
	if(points)
	{
		cout<<"Memory allocation successed. Ending program"<<endl;
		delete[] points;
	}	
	else{
		cout<<"Memory allocation failed. Ending program"<<endl;
	}
   return 0;
}

引用

  1. 引用是相应变量的别名。

    int flag = 30;
    int& temp = flag;
    
  2. const修饰引用:禁止通过引用修改它所指向变量的值。

  3. 引用向函数传递参数的优点之一:可避免将形参赋值给形参,从而极大提高性能。

    #include <iostream>
    using namespace std;
    
    //如果函数接收非常大的对象,则按值传递的开销将非常大,通过使用引用,可极大地提高函数调用的效率。别忘了将const用于引用参数,除非函数需要将结果储存于参数中。
    void GetSquare(const int& number, int& result)
    {
    	result = number*number;
    }
    
    int main()
    {
    	int number = 0;
    	cin >> number;
    	
    	int square = 0;
    	
    	GetSquare(number,square);
    	
    	cout<<number<<"^2 = "<<square<<endl;
    	
       return 0;
    }
    

类和对象

拷贝构造函数

什么是浅拷贝?只有普通变量初始化的拷贝构造函数就浅拷贝。咋算普通变量?如int,char, string…不涉及指针变量。可以直接使用默认拷贝构造函数。

什么是深拷贝?不用默认拷贝构造函数,自己显式定义一个拷贝构造函数,并且在其内部再次分配动态内存,这就是深拷贝。总的来说,就是类中涉及到指针变量,需要在拷贝构造函数内部申请一遍。

使用浅拷贝还是深拷贝,最直接判断方式是看一下类中有没有指针变量。

  1. 浅拷贝:如果类中含有指针变量,导致多个对象共用同一块资源,同一块资源释放多次,崩溃或者内存泄漏。

    img

#include <iostream>
using namespace std;

// 深浅拷贝操作

class Person
{
public:
    // 无参构造函数
    Person()
    {
        cout << "Person的构造函数调用" << endl;
    }
    // 有参构造函数
    Person(int a, int h)
    {
        m_Age = a;
        m_Height = new int(h);
        cout << "Person的有参构造函数调用" << endl;
    }
    // 析构造函数
    ~Person()
    {
        // 将堆区开辟的空间释放掉
        if(m_Height != NULL)
        {
            delete m_Height;
            m_Height = NULL;    // 防止野指针出现
        }
        cout << "Person的析构造函数调用" << endl;
    }

    // 自己实现拷贝构造函数,解决浅拷贝带来的问题
    //务必将接受源对象的参数声明为const引用
    Person(const Person &p)
    {
        cout << "Person拷贝造函数调用" << endl;
        m_Age = p.m_Age;
        // m_Height = p.m_Height;   // 编译器默认实现就是这行代码(浅拷贝)
        // 深拷贝操作
        m_Height = new int(*p.m_Height);
    }

    int m_Age;  // 年龄
    int *m_Height; // 身高
};

void test01()
{
    Person p1(18, 160);
    cout << "Person的年龄:" << p1.m_Age << "身高为:" << *p1.m_Height << endl;

    Person p2(p1);  // 执行来浅拷贝操作(调用默认的拷贝函数)
    cout << "P2的年龄:" << p1.m_Age << "身高为:" << *p2.m_Height << endl;
}

int main(int argc, char const *argv[])
{
    /* code */
    test01();

    return 0;
}

preview

单例类

28.C+± 单例类模板(详解) - 诺谦 - 博客园 (cnblogs.com)

explicit

  1. explicit关键字的作用就是防止类构造函数的隐式自动转换.

  2. explicit关键字只对有一个参数的类构造函数有效, 如果类构造函数参数大于或等于两个时, 是不会产生隐式转换的, 所以explicit关键字也就无效了.

  3. explicit关键字只需用于类内的单参数构造函数前面。由于无参数的构造函数和多参数的构造函数总是显示调用,这种情况在构造函数前加explicit无意义。

  4. google的c++规范中提到explicit的优点是可以避免不合时宜的类型变换,缺点无。所以google约定所有单参数的构造函数都必须是显示的,只有极少数情况下拷贝构造函数可以不声明称explicit。例如作为其他类的透明包装器的类。

  5. effective c++中说:被声明为explicit的构造函数通常比其non-explicit兄弟更受欢迎。因为它们禁止编译器执行非预期(往往也不被期望)的类型转换。除非我有一个好理由允许构造函数被用于隐式类型转换,否则我会把它声明为explicit,鼓励大家遵循相同的政策。

继承(inheritance)

  1. is-a:公有继承

    子类、子类的对象可以访问基类的public成员。

  2. has-a:

    • 保护继承

      子类可以访问基类的public和protect方法,但子类的对象不能访问基类的public成员。

    • 私有继承

      子类、子类的对象不能访问基类的public成员。

  3. 使用final禁止继承(C++11)

多态(polymorphism)

使用虚函数实现多态

  1. 对于将被派生类覆盖的基类方法,务必将其声明为虚函数。

  2. 对基类析构函数,务必提供一个虚析构函数。

纯虚函数

  • 纯虚函数导致类变成抽象基类,且在派生类中必须提供虚函数的实现。
  • 抽象基类无法被实例化。
//抽象基类(ABC)
class AbstractBase
{
public:
    virtual void DoSomeThing() = 0; //纯虚函数
};
//上述声明告诉编译器,AbstractBase的派生类必须实现方法DoSomeThing();
class Derived: public AbstractBase
{
public:
    void DoSomeThing()
    {
        cout<<"Implemented virtual function"<<endl;
    }
};

虚继承

  1. 如果派生类可能被用作基类,派生它 最好使用关键字virtual:

    class Derived1 : public virtual Base
    {
        
    };
    class Derived2 : public virtual Base
    {
        
    };
    //并且使用关键字final禁止将SubDerived作为基类
    class SubDerived final: public Derived1, public Derived2
    {
        
    };
    

    在继承层次结构中,继承多个从同一个类派生而来的基类时,如果这些基类没有采用虚继承,将导致二义性。这种二义性被称为菱形问题(Diamond Problem)。

override(C++11)

在派生类中声明要覆盖基类函数的函数时,务必使用override。

override提供了一种强大的途径,让程序员明确地表达对基类的虚函数进行覆盖的意图,进而让编译器做如下检查:

  • 基类函数是否是虚函数?
  • 基类中相应虚函数的特征标识是否与派生类中被声明为override的函数完全相同?
class Basevirtual void f();
};

class Derived : public Base {
void f() override; // 表示派生类重写基类虚函数f
void F() override;//错误:函数F没有重写基类任何虚函数
};
  • overide作用:在派生类中提醒自己要重写这个同参数函数,不写则报错。

运算符

  1. 理论上来说,前缀运算符优于后缀运算符。++value优于value++
  2. 对于有原始指针的类,务必实现拷贝构造函数和重载赋值运算符。

运算符重载

return_type operator operator_symbol(...parameter list...)
#include<iostream>
using namespace std;

class Box
{
    double length;  //长度
    double width;   //宽度
    double height;  //高度
public:
    Box(){}
    Box(double length,double width, double height):length(length),width(width),height(height){}
    ~Box(){}
    void print(void)
    {
        cout<<length<<","<<width<<","<<height<<endl;
    }
    
    //重载 + 运算符,  实现Box相加
    Box operator + (const Box& b)
    {
        Box box;
        box.length = this->length + b.length;
        box.width = this->width + b.width;
        box.height = this->height + b.height;
        return box;
    }
    
    //重载前缀运算符 ++
    Box operator ++ ()
    {
        ++this->length;
        ++this->width;
        ++this->height;
        return *this;
    }
    
    //重载后缀运算符 --
    //注意,int 在 括号内是为了向编译器说明这是一个后缀形式,而不是表示整数。只是为了区分前置后置
    Box operator -- (int)  //加上int 
    {
        //保存原始值
        Box temp = *this; //隐式调用转换函数
        //Box temp(*this); //调用默认拷贝构造函数
        //Box temp(length,width,height);
        --this->length;
        --this->width;
        --this->height;
        return temp; //返回原始值
    }
    
    /*在这里,有一点很重要,我们需要把运算符重载函数声明为类的友元函数,这样我们就能不用创建对象而直接调用函数。习惯上人们是使用 cin>> 和 cout<< 的,得使用友元函数来重载运算符,如果使用成员函数来重载会出现 d1<<cout; 这种不自然的代码。
    */
    //相当于在整个全局声明了一个重载运算符。当重载的运算符函数是全局函数时,需要在类中将该函数声明为友元
    //重载 >>
    friend istream& operator >> (istream& input, Box& b)
    {
        input >> b.length >> b.width >> b.height;
        return input;
    }
    
    //重载 <<
    friend ostream& operator << (ostream& output,const Box& b)
    {
        output<<b.length<<","<<b.width<<","<<b.height;
        return output;
    }
};

int main()
{
    Box box1(5.0,6.0,7.0);
    Box box2(8.0,9.0,10.0);
    Box box3 = box1 + box2;
    box3.print();  //13,15,17
    
    Box ox4 = ++box3;
    box4.print(); //14,16,18
    
    Box box5 = box3--;
    box5.print(); //14,16,18
    
    box3.print(); //13,15,17
    
    Box box6;
    cin >> box6;
    cout <<box6<<endl;
    return 0;
}

C++类型转换运算符

destination_type result = cast_operator<destination_type> (object_to_cast)

static_cast

  • 用于在相关类型的指针之间转换。
Derived objDerived1 = new Derived();
Base* objBase = &objDerived1;  //OK!向上转换,无需显式转换

Derived* objDerived2 = objBase;//Error! 向下转换,需要显式转换

//OK!
Derived* objDerived2 = static_cast<Derived*>objBase; 
//static_cast只验证指针类型是否相关,而不执行任何运行阶段的检查。
Base* objBase = new Base();
Derived* objDer = static_cast<Derived*>objBase;//Not Error!但有可能导致意外结果。
  • 将原本隐式的标准数据类型的类型转换 转换为 显式。

dynamic_cast

  • 与static_cast相反,dynamic_cast在运行阶段执行转换,可检查dynamic_cast操作的结果,以判断类型转换是否成功。

    Base* objBase = new Derived();
    
    Derived* objDer = dynamic_cast<Derived*>(objBase);
    
    //务必检查dynamic_cast的返回值,看它是否有效。如果返回值为NULL,说明转换失败。
    if(objDer)
    {
        objDer->CallDerivedFunction();
    }
    
  • 这种在运行阶段识别对象类型的机制称为 运行阶段类型识别(runtime type identification, RTTI

reinterpret_cast

  • 强制转换。
  • 应尽量避免使用reinterpret_cast。

const_cast

  • 让程序员能够关闭对象的访问限制符const。

    
    //DisplayMembers()本应该为const的,但没有这样定义。并且类SomeClass属于第三方库,无法对其进行修改。
    void DisplayAllData(const SomClass& object)  //显然 这里使用const是应该的
    {
        object.DisplayMembers();//编译错误
        //原因:使用const引用调用non-const的成员。
    }
    
    void DisplayAllData(const SomClass& object)
    {
        SomeClass& refData = const_cast<SomeClass&>(object);
        object.DisplayMembers();//OK!
    }
    
  • 非必要不使用const_cast来调用非const函数。这样导致的结果不可预料!

除dynamic_cast外的类型转换都是可以避免的;应尽量避免使用类型转换。

使用#define定义常量

  • 预处理只是进行死板的文本替换,而不检查替换是否正确。
#define PI 3.1415926 //再预处理器看来,无法确定其数据类型

//定义常量时,更好的方法是使用const
const double PI = 3.1415926;
typedef double MY_DOUBLE; //#define MY_DOUBLE double

使用宏避免多次包含

  • 预处理器编译指令: #ifndef #endif

    #ifndef _HEAD_H_
    #define _HRAD_H_
    
    //head.h的内容
    
    #endif   //end of head.h
    

使用#define编写宏函数

  • 注意使用括号保证优先级

使用assert宏验证表达式

  • 需包含#include<assert.h>
  • assert用来调试,对输入的参数进行验证。
  • assert()在发布模式下不可用。
  • 推荐在代码中大量使用assert(),虽然在发行版本被禁用,但对提高代码质量很有帮助。

尽量不要自己编写宏函数;使用const常量而不是宏常量。宏不是类型安全的。

模板

  • 模板无疑是C++语言中最强大的特性之一。模板是类型安全的。
  • 在编译器看来,仅当模板被使用时,其代码才存在。
  • 对模板来说,实例化是指使用一个或多个模板参数来创建特定的类型。

模板声明 template

template<typename objType>

模板函数

#include<iostream>
using namespace std;

//模板函数
template<typename objType>
const objType& GetMax(const objType& value1, const objType& value2)
{
    return value1 > value2 ? value1 : value2;
}

int main()
{
    int num1 = 25, num2 = 40;
    //int maxVal = GetMax<int>(num1,num2); //模板函数可以不显式指定类型,但模板类必须显式指定。
    int maxVal = GetMax(num1,num2);
    cout<<maxVal<<endl;
    
    return 0;
}

模板类

#include<iostream>
using namespace std;

//模板类
template<typename T1, typename T2 = T1> //T2的类型默认为T1
//template<typename T1 = int, typename T2 = string> //声明默认参数的模板
class MyTemplate
{
    T1 member1;
    T2 member2;
public:
    MyTemplate(const T1& t1, const T2& t2):member1(t1),member2(t2){}
    const T1 getObj1() const{return member1;}  //返回值为const ; this指针为const
    T2 getObj2(){return member2;}
    //...
};

//使用
int main()
{
    MyTemplate<int ,double> temp1(10,19.5);
    MyTemplate<int ,string> temp2(10,"hello world!");
    cout<<temp1.getObj1()<<endl;
    cout<<temp2.getObj2()<<endl;
    return 0;
}

参数数量可变的模板(C++11)

泛化之美–C++11可变模版参数的妙用 - qicosmos(江南) - 博客园 (cnblogs.com)

//这里class关键字表明T是一个类型,后来为了避免class在这两个地方的使用可能给人带来混淆,所以引入了typename这个关键字,它的作用同class一样表明后面的符号为一个类型
//template <class... T>   
template<typename... T>
void f(T... args);

sizeof...(args)  //sizeof...()打印可变参的个数

上面的可变模版参数的定义当中,省略号的作用有两个:

  1. 声明一个参数包T… args,这个参数包中可以包含0到任意个模板参数;
  2. 在模板定义的右边,可以将参数包展开成一个一个独立的参数。

我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数。

展开可变模版参数函数的方法一般有两种:一种是通过递归函数来展开参数包,另外一种是通过逗号表达式来展开参数包

  1. 递归函数方式展开参数包

    
    #include <iostream>
    using namespace std;
    
    //通过递归函数展开参数包,需要提供一个参数包展开的函数和一个重载的递归终止函数,递归终止函数正是用来终止递归的。
    //递归终止函数
    template <typename T>
    void print(T head)
    {
       cout << "parameter " << head << endl;
    }
    //展开函数
    template <typename T, typename ...Args>
    void print(T head, Args... rest)
    {
       cout << "parameter " << head << endl;
       print(rest...);
    }
    
    int main(void)
    {
       print(1,2,3,4);
       return 0;
    }
    
    
  2. 逗号表达式展开参数包

    #include <iostream>
    using namespace std;
    
    //这种方式需要借助逗号表达式和初始化列表
    template <typename T>
    void printarg(T t)
    {
       cout << t << endl;
    }
    
    template <typename ...Args>
    void expand(Args... args)
    {
       int arr[] = {(printarg(args), 0)...};
    }
    
    int main(void)
    {
       expand(1,2,3,4);
       return 0;
    }
    

static_assert(C++11)

  1. 执行编译阶段断言:禁止不希望的模板实例化。

    //禁止针对int实例化模板
    static_assert(sizeof(T) != sizeof(int),"No in please!");  //不满足条件禁止编译
    

标准模板库

STL容器

容器是用于存储数据的STL类。

顺序容器

顾名思义,顺序容器按顺序存储数据,如数组和列表。插入速度快,查找满。

  • std::vector:常规动态数组。
  • std::deque:动态数组,但允许在开头插入或删除。
  • std::list:双向链表。
  • std::forward_list:单向链表。
关联容器

按指定的顺序存储数据。插入慢,查找快。

  • std::set:集合
  • std::unordered_set
  • std::map:键-值对。
  • std::unordered_map
  • std::multiset:类似set,不要求值唯一的,可存储多个值相同的项。
  • std::unordered_multiset
  • std::multimap:类似map,不要求键是唯一的。
  • std::unordered_multimap
容器适配器(Container Adapter)
  • std::stack:栈
  • std::queue:队列
  • std::priority_queue

STL迭代器

STL算法

STL字符串类

  • std::string:简单字符串
  • std::wstring:宽字符串

STL string类

  • #include
  • std::string 具体化的是std::basic_string模板类

实例化string

#include <string>
#include <iostream>

using namespace std;

void print(const string& str,const string& s)
{
    cout<<s<<": "<<str<<endl;
}

int main()
{
    string str0;        //生成空字符串str
    string str1 = "Hello String!";
    const char* Cstr = "Hello Char*";
    string str2(str1,6);  //从"索引为5"的位置开始拷贝
    string str3(Cstr,5);  //将C字符串前5个字符作为字符串s的初值。
	string str4("Hello String!",5);
    string str5(10,'a');  //生成一个字符串,包含n个c字符
    string str6(str1,0,5);//将字符串str内“始于index且长度顶多n”的部分作为字符串的初值
    string str7(str5.begin(),str5.begin()+5);
	
	print(str0,"str0");
    print(str1,"str1");
    print(str2,"str2");
    print(str3,"str3");
    print(str4,"str4");
    print(str5,"str5");
	print(str6,"str6");
    print(str7,"str7");
    
	/*输出
	str0: 
	str1: Hello String!
	str2: String!
	str3: Hello
	str4: Hello
	str5: aaaaaaaaaa
	str6: Hello
	str7: aaaaa
	*/
    return 0;
}

string增删改查

#include <string>
#include <iostream>
#include <algorithm>

using namespace std;

//遍历获取元素
void display()
{
    //size(),length()  //返回字符数量
    string str("hello world!");
    //size_t 作为unsigned int 和unsigned long 的别名,编译器根据不同的系统类型替换标准类型,旨在增强移植性
    for(size_t i = 0; i < str.length(); ++i)
    {
        cout<<"str["<<i<<"]: "<<str[i]<<endl;
    }
    cout<<endl;
    
    //逆序迭代器方式
    for(auto t = str.rbegin(); t != str.rend(); ++t)
    {
        cout<<*t<<",";
    }
    cout<<endl;
    
    cout<<str.c_str()<<endl;//将内容以C_string返回
}

//拼接字符串
void append()
{
    //+=,append()
    //push_back() //在尾部添加字符
    string str1("Hello World!");
    string str2("Hello Programer!");
    
    str1 += str2;  //+=
    cout<<" str1 += str2, str1: "<<str1<<endl; 
    
    str1.append(str2); //append()
    cout<<" str1.append(str2)2, str1: "<<str1<<endl;
    
    str2.push_back('?');
    cout<<" str2.push_back('?'), str2: "<<str2<<endl;  //push_back()在末尾添加字符
}

//查找字符(串)
void find()
{
    string s("dog bird chicken bird cat");
    
    cout <<	s.find("bird") << endl;  //查找子串s,返回找到的位置索引,
	cout << (int)s.find("pig") << endl;   //-1表示查找不到子串

	cout << s.rfind("bird") << endl; //反向查找子串s,返回找到的位置索引,
	cout << s.rfind('i') << endl;    

	cout << s.find_first_of("13r98") << endl; //查找第个属于某子串的字符
	cout << s.find_first_not_of("dog bird 2006") << endl;//查找第一个不属于某字符串的字符
    
}
//删除
void erase()
{
    string s("123456789abcd!");
    //iterator erase(iterator p);//删除字符串中p所指的字符
    s.erase(s.begin());
    cout << s << endl;  //out: 23456789abcd!
    //iterator erase(iterator first, iterator last);//删除字符串中迭代器区间[first,last)上所有字符
    s.erase(s.begin()+2,s.begin()+3);
    cout << s << endl; //out: 2356789abcd!
    //string& erase(size_t pos = 0, size_t len = npos);//删除字符串中从索引位置pos开始的len个字符
    s.erase(0,2);
    cout << s << endl; //out: 56789abcd!
    //void clear();//删除字符串中所有字符
    s.clear();
    cout << s << endl;
}

//比较
void compare()
{
    string s1("abcdef"), s2("abc");
    
    //相等返回0,大于返回 正数,小于返回  负数
    cout << s1.compare("abcdef") << endl;  //相等,打印0
	cout << s1.compare(s2) << endl;   //s1 > s2,打印 >0
	cout << s1.compare("abyz") << endl; //s1 < "abyz",打印 <0
	cout << s1.compare(0,3,s2) << endl; //s1的前3个字符==s2,打印0
}

//反转算法
//需要 头文件是#include <algorithm>
void reverse()
{
    string str("hello string!");
    cout<<"str: "<<str<<endl;
    reverse(str.begin(),str.end());
    cout<<"reverse str: "<<str<<endl;
}

int main()
{
    //display();
    //append();
    //erase();
    find();
    compare();
    reverse();
    
    return 0;
}

STL vector类

  • 需包含头文件#include <vector>

实例化vector

#include <vector>

using namespace std;

int main()
{
    vector<int> intArray;  //默认构造
    
    vector<float> floatArray{20.4, 15.9}; //初始化两个元素的值
    
    vector<int> intArray2(10); //初始化包含10个元素
    
    vector<int> intArray3(10, 1);//初始化包含10个元素,且值都为1
    
    vector<int> intArray4(intArray3); //使用一个实例初始化
    return 0;
}

vector增删改查

#include <vector>
#include <iostream>

using namespace std;

//遍历
void  traverse()
{
    vector<int> temp{10,12,14,16};
    
    for(auto t : temp)
    {
        cout<<t<<" ";
    }
    cout<<endl;
    
    cout<<"temp[3]: "<<temp[3]<<endl; //注意 索引不能越界
    cout<<"temp[2]: "<<temp.at(2)<<endl; //at()在运行阶段执行检查  更安全
}
void  traverse(const vector<int>& temp)
{
    cout<<"size = "<<temp.size()<<": ";
    for(auto t : temp)
    {
        cout<<t<<" ";
    }
    cout<<endl;
}
//插入、删除
void insert()
{
    vector<int> integers;
    
    //push_back()在末尾添加元素,首选
    integers.push_back(1);
    integers.push_back(2);
    traverse(integers);
    
    //insert()在指定位置插入元素,效率低
    integers.insert(integers.begin()+1, 25); //在第一个元素后面插入25
    integers.insert(integers.end(),2,45);  //在数组末尾插入2个元素,值都为45
    traverse(integers);
    
    vector<int> temp = {15,16};
    integers.insert(integers.begin(), temp.begin(), temp.end());//插入temp的值到数组开头
    traverse(integers);
    
    //pop_back()从末尾删除元素
    integers.pop_back();
    traverse(integers);
    
    //清空元素
    integers.clear();
    traverse(integers);
    
    //判空
    if(integers.empty())
    {
        cout<<"The container is now empty!"<<endl;
    }
}


int main()
{
    traverse();
    insert();
    
    return 0;
    
}
  • size():实际存储的元素数;capacity():总的容量。 数组的大小 < = <= <= 容量。(如果数组进行了插入,vector需要重新分配内部缓冲区,重新分配的逻辑实现是智能的:提前分配更多的容量)。
  • 重新分配内部缓冲区时,需要复制容器中包含的对象,这可能降低性能。

STL deque类

  • 需包含#include <deque>
  • 具有vector的所有性质。
  • 使用push_frontpop_front在开头插入/删除元素。

STL list类

  • 需包含#include<list>
  • 双向链表。
  • 迭代器可以++或者--
  • 虽然STL提供了sort()和remove()两种算法,但是list也提供了这两种算法。这些算法的成员函数版本**确保元素的相对位置发生变化后指向元素的迭代器仍然有效。**

实例化list

#include<list>
#include<iostream>

using namespace std;

int main()
{
    list<int> linkInts0;   //初始化空链表
    
    list<int> linkInts1(10); //初始化包含10个元素的list
    
    list<int> linkInts2(10,99);//包含10个元素,均为99
    
    list<int> linkInts3(linkInts2);//复制构造
    
    vector<int> vecInts(10,200);
    list<int> linkInts4(vecInts.begin(),vecInts.end());//从vector对象中复制
    
    return 0;
}

list增删改查

#include<list>
#include<iostream>

using namespace std;


//遍历
template<typename T>
void display(const T& container)
{
    for(auto element = container.cbegin(); element != container.cend(); ++element)
    {
        cout<<*element<<" ";
    }
    cout<<endl;
   
}
//插入
void insert()
{
    list<int> listInts;
    
    listInts.push_back(10);
    listInts.push_back(20);
    listInts.push_front(0);
    display(listInts);
    
    //iterator insert(iterator pos, const T& x)
    /*
    既然是链表,其迭代器就无法进行加减运算,但可以自增自减
    */
    //listInts.insert(listInts.begin()+1, 5);// error: no match for ‘operator+’ (operand types are ‘std::_Fwd_list_iterator<int>’ and ‘int’)
    listInts.insert(++listInts.begin(), 5); //(插入位置,值)
    display(listInts);
    //void insert(iterator pos, sizer_type n, const T& x)
    listInts.insert(listInts.end(), 2, 0); //(插入位置,插入个数,值)
    display(listInts);
    /*template<class InputIterator>
     void insert(iterator pos, InputIterator f, InputIterator l);
    */
    list<int> listInts1;
    listInts1.insert(listInts1.begin(), listInts.begin(), listInts.end());
    display(listInts1);
}
//删除
void erase()
{
    list<int> listInts{0,10,20,30,40};
    
    listInts.erase(listInts.begin()); //删除
    display(listInts);
    
    listInts.clear(); //清空
}
//反转、排序
bool Sort_Desc(const int& t1, const int& t2)  //二元谓词,告诉sort如何解释小于。
{
    return (t1 > t2); 
}
void sort()
{
    list<int> listInts{-20,20,10,100,40};
    //反转
    listInts.reverse();
    display(listInts);
    
    //排序
    //默认排序
    listInts.sort();
    display(listInts);
    
    //高级排序
    listInts.sort(Sort_Desc);  //降序
    display(listInts);
}

int main()
{
    sort();
    return 0;
}
  • 对包含对象的list进行排序
    • 在list包含的对象所属类中,实现运算符<;
    • 提供一个排序二元谓词。

STL forward_list(C++11)

  • 单向链表
  • 与list类似
  • 插入元素不能使用push_back;而只能使用push_front
  • 对迭代器只能使用++,不能使用--
  • 相比list,占用的内存稍少,因为只需指向下一个元素,而无需指向上一个元素。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值