Skip to content

Latest commit

 

History

History
393 lines (298 loc) · 20.5 KB

1.C++基础.md

File metadata and controls

393 lines (298 loc) · 20.5 KB

C++ 基础

标准库类型 string

  • string对象的初始化

    string s1; //四种初始化的方法
    string s2 = s1;
    string s3 = "hiya";
    string s4(10,'c');

    区别拷贝初始化和直接初始化

  • stringsizeempty操作 empty根据string对象是否为空返回一个对于的布尔值; size函数返回string对象的长度;

  • string::size_type类型 在size函数中返回类型是string::size_type

  • 比较string对象 相等性运算==!= 关系运算< <= > >=

  • string对象赋值

  • 两个string对象相加 两个string对象相加实现将两个字符串相拼接

  • 字面值和string相加 需要保证加法运算符两侧至少有一个string对象。

  • 使用基于范围for语句

    for( declaration : experision)
        statement
    //其中declaration为迭代器 会被初始化为experision部分的下一个元素值

    使用范围for循环时如果要改变遍历对象的值需要在定义循环变量时使用引用

  • 使用下标执行迭代

    for( decltype(s.size) index = 0; index != s.size() && !isspace(s[index]); ++index)
        s[index]= toupper(s[index]);

标准库类型 vector

  • 初始化vector 由模板控制着定义和初始化的方法。

    在初始化对象时可以使用多种初始化的方式,多数情况下这些方式可以相互等价的使用。也有例外的情况:其一:使用拷贝初始化时(使用=时)只能提供一个初始值,其二:如果提供的是一个类内初始值时只能使用拷贝初始化或者使用花括号的初始化,其三、如果是提供初始元素值列表时,则只能把初始值放在花括号里使用列表初始化。

    在进行vector对象初始化时应区别圆括号和花括号,区别使用的初始值是vector对象中元素的个数还是元素的初始值

vector<int> ivec;

vector<int> ivec2(ivec);  //拷贝初始化
vector<int> ivec3 = ivec; //拷贝初始化

vector<string> articles = {"a","an","the"}; //使用列表初始化
vector<string> v1{"a","an","the"};  // 使用列表初始化

vector<int> ivec(10,-1); //创建指定数量的元素来初始化 10个int型的元素 都被初始化为-1

vector<int> ivec(10); //使用这种初始化有一定限制:有些类必须要求提供初始值,如果该vector对象类型不支持默认初始化则无法完成 
  • 向vector对象中添加元素 使用push_back()函数向vector中添加元素。push_back()函数将一个值当作vector对象添加到vector对象的尾部。
for (int i = ; i!=100; ++i)
{
    v2.push_back(i);
}
  • vector支持的操作
vector<int> v

v.empty()
v.size()
v.empty()
v[n]          //返回v中第n个位置上元素的引用
  • 不能永下标形式添加元素 因为不能使用下标访问一个空vector对象,只能通过push_back()vector对象中添加元素

迭代器

与指针类似,可以永迭代器来实现对对象的间接访问,其对象是容器中的元素或者string对象中的字符。可以使用迭代器来访问元素,迭代器也能从一个元素移动到另一个元素,或者指向容器中尾元素的下一位置。(其他位置均无效)

  • 获取迭代器 使用有迭代器类型的成员函数来返回迭代器。一般是名为begin()end()的成员。其中begin成员函数返回指向第一个元素的迭代器,end()成员函数返回指向容器(string对象)“尾元素的下一位置(one past the end)”的迭代器。一般称作尾后迭代器。当容器为空时,beginend返回的时同一个迭代器。

  • 迭代器运算符 可以对迭代器进行一些运算。比如使用“=”和“!=”来比较两个迭代器是否相等。当两个迭代器指向的元素相同或者是同一个容器的尾后迭代器,则它们相等,否则不相等。

iter = ivec.begin();

*iter      //使用解引用iter来访问迭代器指向的元素
iter->mem  //解引用iter并获取该元素的名为mem的成员
++iter     //移动迭代器指示容器下一个元素
--iter     //移动迭代器指示容器前一个元素

iter1 == iter2 //判断两个迭代器是否相等
  • iterator类型和const_iterator类型 const_iterator是常量类型在使用时只能读取但不能修改迭代器指向的元素的值。iterator类型迭代器指向的对象可读可写。

    begin()end()函数返回的迭代器由对象是否是常量决定,如果对象是常量则begin()end()返回的是const_iterator

    还可以使用cbegin()cend()来返回const_iterator

  • 结合使用解引用和成员访问操作 使用解引用获得迭代器指向的对象,如果该类型是类可以进一步访问该对象的成员。例如:

vector<string> vc1;  //vc1是string类型的容器

auto it = vc1.begin();

(*it).empty();  //判断迭代器it指向的string对象是否为空
it->empty();  //与上一条等价,对于指针和迭代器类型使用箭头运算符

因为vector对象可以动态增长,对于任何一种可能改变vector对象容量的操作,比如push_back,都会使该对象的vector对象的迭代器失效。

  • 迭代器运算 stringvector的迭代器有额外的运算符:
iter + n;  //使迭代器向前移动n个位置
iter - n;  //使迭代器向后移动n个位置
iter1 - iter2 //返回iter1和iter2之间的距离,该类型是difference_type类型,在vector和string中都定义了该类型

auto mid = (v1.begin() + v1.size()) / 2   //找到指向某vector对象中间位置的元素

if( it < mid ) //用来判断迭代器it和mid谁前谁后

类的基本思想是数据抽象(data abstraction)和封装。数据抽象是一种依赖于接口(interface)和实现(implementation)分离的编程(以及设计)技术。类的接口包括用户所能执行的操作;类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。封装实现了类的接口和实现的分离。封装后的类隐藏了其实现细节,类的用户只能使用接口而无法访问实现部分

  • 类的成员函数
    定义和声明成员函数的方式和普通函数差不多。成员函数的声明必须在类内部,定义则既可以在类内部也可以在类外部。作为接口组成部分的非成员函数,其定义和声明都在类外部。

    //类的成员函数
    struct Sales_date{
        std::string isbn(){return bookNo;}
        Sales_data& combine(const Sales_data&);
        double avg_price() const;
    
        std::string bookNo;
        unsigned units_sold = 0;
        double revenue = 0.0;
    }
    //类的非成员接口函数
    Sales_data add(const Sales_data&, const Sales_data& );
    std::ostream &print(std::ostream& , const Sales_data& );
    std::istream &read(std:: istream&, Sales_data& );
    
  • this

    成员函数通过一个名为this的额外的隐式参数来访问调用它的那个对象。当调用成员函数时,用请求该函数的对象的地址初始化this。在成员函数内部,可以直接使用调用该函数的对象的成员,而无需通过成员访问运算符来进行访问,任何对类成员的访问都被看做是this的隐式引用。this是一个常量指针

  • const成员函数
    默认情况下,this的类型是指向类类型非常量版本的常量指针。所以无法在常量对象上调用普通的成员函数。当成员函数不会改变对象成员的值时,将this设置为指向常量的指针有助于提高函数的灵活性。
    由于this参数是隐式的,C++中运行将const关键字放在函数的参数列表之后。

     double avg_price() const;

    像这样使用const的成员函数被称作常量成员函数

    Tips: 常量对象,以及常量对象的引用或指针都只能调用对象的常量成员函数

  • 类作用域和成员函数

    编译器分两步处理类:首先编译成员声明,然后才轮到成员函数体,因此,成员函数体可以随意使用类中其它成员而无需在意成员出现的顺序。

  • 返回this对象的函数

    Sales_data& Sales_data::combine(const Sales_data &rhs)
    {
        units_sold += rhs.units_sold;
        revenue += rhs.revenue;
        return *this; //返回调用该函数的对象的引用
    }

定义与类相关的非成员函数

类的作者常常需要定义一些辅助函数,尽管这些函数的定义的操作从概念上讲属于类的接口部分,但其并不属于类。如果函数在概念上属于类,但是不定义在类中,则它一般应与类声明在同一个头文件内。

构造函数

类定义了其对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数

构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数

  • 构造函数的名字和类名相同
  • 构造函数无返回类型
  • 构造函数有一个参数列表(可能为空)和一个函数体(可能为空)
  • 类可以包含多个构造函数
  • 构造函数不能被声明为const
  • 默认构造函数

    对于没有提供初始值的对象,其执行力默认初始化。类通过一个特殊的构造函数来控制默认初始化的过程,这个构造函数被称为默认构造函数

    由编译器创建的构造函数又被称为合成的默认构造函数,对于大多数类,其初始化类的数据成员的规则是:

    • 如果存在类内初始值,用它来初始化 *否则,默认初始化该成员

    只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数

    =default在C++11新标准中,如果我们需要默认行为,可通过在参数列表后面写上=default来要求编译器合成默认构造函数。

  • 构造函数初始值列表 使用构造函数初始化列表:

    Sales_data(const std:: string&s):bookNo(s){}
    Sales_data(const std:: string&s ,unsigned n,double p):
                bookNo(s),units_sold(n),revenue(p*n){}

    当某个数据成员被构造函数初始值列表忽略时,它将以与合成默认构造函数相同的方式隐式初始化。

  • 拷贝、赋值、析构

    除了要控制类的对象如何初始化之外,类还需要控制拷贝、赋值和销毁对象时发生的行为。如果不主动定义这些操作,则编译器将替我们合成它们。

    某些类不能依赖于合成的版本,如管理动态内存的类。

  • 访问控制与封装

    在C++中使用访问说明符(access specifiers)加强类的封装性,使类的用户无法访问对象的内部而控制具体实现细节

    定义在public说明符之后的成员在整个程序内可被访问,public成员定义类的接口
    定义在private说明符之后的成员可以被类的成员函数访问,但不能被使用类的代码访问,private部分封装了类的实现细节

    classstruct关键字:
    两个都可以定义一个类,唯一的区别是,两者的默认访问权限不一样。
    类可以在第一个访问说明符之前定义成员,这些成员的访问权限依赖于类定义的方式。使用struct关键字,则这类成员是public,相反,使用class这类成员则是private

    class 和 struct 定义的唯一区别就是默认访问权限。

  • 友元
    有时我们需要定义一些并非类的成员函数的接口函数,而这部分函数无法访问到类的非公有部分。

    类可以允许其它类或者函数访问它的非共有成员,方法是令其他类或者函数称为它的友元(friend)。如果类想把一个函数作为友元,只需要增加一条friend关键字开始的函数声明语句即可。友元只能出现在类内部,但位置不限,友元不是类的成员也不受它所在访问控制级别的约束。

    一般来讲,最好在类的开始或者结束处集中声明友元

  • 友元的声明

    友元声明仅仅指定了访问权限,而非通常意义上的函数声明,所以为了使友元对类的用户可见,必须在友元声明之外,再专门对函数声明一次。通常将友元的声明与类本身放在同一个头文件中。

类的其它一些特性

  • 类成员

    • 在类内可以定义类型成员:
      在类内,除了定义数据和函数成员之外,类还可以自定义某种类型在类中的别名。由类定义的类型名字和其它成员一样存在访问限制,可以是public或者private中的一种
    class Screen{
        public:
            typedef std::string::size_type pos;
        private:
            pos cursor = 0;
            pos height = 0 , wight = 0;
            std::string contents;
    }

    用来定义类型的成员必须先定义后使用,这一点与普通成员有区别

  • 令成员作为内联函数 定义在类内部的成员函数是自动inline的。我们可以在类的内部把inline作为声明的一部分显示的声明成员函数,也能在类外部用inline关键字修饰定义。最好值在类外部定义的地方说明inline

  • 成员函数的重载 成员函数的重载与非成员函数一样,只要成员函数之间在参数的数量和类型上有所区别。

  • mutable关键字 可变数据成员
    我们有时希望能修改某个数据成员,即使是在const成员函数内。一个可变数据成员永远不会是const,一个const成员函数可以改变一个可变成员的值。

        class Screen{
            public:
                void some_member() const;
            private:
                matable size_t access_ctr; //即使在一个const对象内也能修改
        }
        void Screen::some_member() const
        {
            ++access_ctr;
        }
  • 返回*this的成员函数

    区别返回调用对象的副本和返回调用对象的引用

    从const成员函数返回*this

    一个const成员如果以引用的形式返回 *this 指针,那么它的返回类型将是常量引用

  • 基于const的重载
    通过区分是否是const成员,可以对其进行重载。只能在一个常量对象上调用const成员函数,虽然可以在非常量对象上调用常量版本或非常量版本的函数,但此时非常量版本是一个更好的匹配。

  • 类的声明
    类与函数一样

  • 类之间的友元关系
    类除了将非成员函数定义为友元,还可以把其他的类定义成友元,也可以将其他类的成员函数定义成友元。

    如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员

    class Screen{
        //Screen类将window_mgr类声明为自己的友元
        friend class Window_mgr;
    };

    友元关系不存在传递关系,每个类负责控制自己的友元类或友元函数

  • 令成员函数作为友元
    除了将整个类声明为友元外,还可以将某个类的单个成员声明为友元,将一个成员声明为友元时,我们必须明确指出该成员属于哪个类。

    想要令某个成员函数作为友元时,必须仔细组织程序的结,以满足声明和定义的依赖关系

  • 友元声明和作用域

    友元声明的作用是影响访问权限,其本身并非普通意义上的声明

    类和非成员函数的声明不一定必须在它的友元声明之前,当一个名字第一次出现在一个友元声明中时,隐式假定该名字在当前作用域中是可见的。

    struct X{
        friend void f(){  /*友元可以定义在类的内部*/ }
        X(){ f(); }             //错误:此时f还没有声明 
        void g();
        void h();
    };
    void X::g(){ return f(); }  //错误:此时f还没有声明
    void f();                   //声明定义在X类中的函数
    void X::h(){ return f(); }  //正确:现在f的声明在作用域中了
  • 类的作用域
    每个类定义自己的作用域。在类的作用域之外,普通的数据和函数成员只能由对象、引用或者指针使用成员访问运算符来访问。对于类类型成员则使用作用域运算符访问。

  • 构造函数初始化列表

    在定义变量时习惯于立即对其进行初始化,而非先定义再赋值,对于对象的数据成员而言,初始化和赋值也有区别。如果没有在构造函数的初始值列表中显式地初始化成员,则成员将在构造函数体之前执行默认初始化

    构造函数的初始值有时必不可少

    有时可以忽略数据成员初始化和赋值之间的差异,但并不是所有时候都这样,如果成员是const或者是引用的话,则必须进行初始化。

    初始化const或者引用类型的数据成员的唯一机会就是通过构造函数初始值。进行显式地初始化引用和const成员。初始化方法是在构造函数后使用初始化列表。

  • 隐式的类类型转换

    c++中对于类类型,由只接受一个参数的构造函数来实现 该参数类型到类类型的转换,这是一种隐式转换,有时将这种构造函数称为转换构造函数

        string null_book = "9-99-9999-9";
        item.combine(null_book); //此时sting类型隐式转换为Sales_date类类型
  • explicit关键字
    通过将构造函数声明为explicit来阻止隐式转换,同时关键字explicit只对一个实参的构造函数有效。需要多个实参的构造函数无法进行隐式转换,所以无需指定为explicit。只能在类内声明构造函数时使用explicit关键字。

    当我们用explicit关键字声明构造函数时,它将只能以直接初始化形式使用(区别于拷贝初始化)。并且编译器将不会在自动转换过程中使用该构造函数。

类的静态成员

在一些情况下我们需要类的一些成员直接与类相关,而不是与类的各个对象保持关联。

  • 静态成员的声明
    使用static关键字

    类的静态成员存在于任何对象之外,对象中不包括任何于静态数据成员有关的数据。其静态成员只存在一个,且被所有对象共享。静态成员函数不与任何对象绑定在一起,并且不包含(this)指针。所有静态成员不能声明成const,并且也不能在static函数体内使用this指针(包括显示使用以及调用非静态成员时的隐式使用)

  • 使用类的静态成员
    可以用作用域运算符直接访问静态成员。

    虽然静态成员不属于类的某个对象,但我们仍然可以使用类的对象、引用或者指针来访问静态成员 成员函数可以不用作用域运算符直接访问

  • 定义静态成员
    既可以在类内部也可以在类外部定义静态成员函数。在类外定义静态成员函数时,不能重复static关键字。

    和类的所有成员一样,当我们指向类外部的静态成员时,必须指明成员所属类名。static关键字则只出现在类内部声明语句中。 静态数据成员不属于任何一个对象,所有其并不是在创建对象时被定义的,所以它们不是由类的构造函数初始化的。一般来说,我们不能在类的内部初始化静态成员,相反必须在类的外部定义和初始化每个静态成员,一个静态成员只能初始化一次。

  • 静态成员的类内初始化 通常情况下类的静态成员不应该在类内部初始化,但是可以在类内部为静态成员提供const整数类型的类内初始值,但要求静态成员必须是字面值常量类型constexpr。

    即使一个常量静态数据成员在类内部被初始化了,通常情况下也应该在类的外部定义一下该成员

  • 不完全类型
    类的数据成员不能是类本身类型,但可以是指向类的指针。
    静态成员可以是不完全类型,指针成员可以不完全类型

    class Bar{
        public:
        //....
        private:
            static Bar mem1;  //正确:静态成员可以是不完全类型
            Bar *mem2;        //正确:指针成员可以是不完全类型
            Bar mem3;         //错误:数据成员必须是完全类型
    }