欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++基础知识

程序员文章站 2022-07-12 12:36:13
...

 

第一天 C++基础班 ************************************=============================================

1.封装,继承,多态。

2.作用域运算符(::)  

    :: 前面不写东西,表示调用全局变量

3.名字控制(命名空间(namespace)):

    1)命名空间只能在全局范围内定义

    2)命名空间可以嵌套命名空间

    3)命名空间是开放的,可以随时向命名空间添加东西(变量,函数,类等)。

    4) 声明和实现可分离

    5) 匿名命名空间,默认此空间中的函数,变量等只在当前文件中有效,相当于给每个变量、函数前加入static.

    namespace TestA

    {

        int a;            //这是在命名空间中未初始化的定义。    当在命名空间进行定义时,而且此命名空间放在头文件,当在其他文件进行

                        //调用时,会产生重复定义的问题,编译不过。

        int b = 9;        //这是在命名空间中初始化的定义

        extern int c;     //这是命名空间中的声明。

    }

 

4.条件编译

   #pragma once  (第一种)

    #ifndef ___  ;  #define _____;    #endif;   ____ 代表一个定义的名字。(第二种)

 

5.bool 类型: bool 类型的变量可以把"任何非0值"转换为1.

 

6.三目运算符:C和C++的区别:

    C语言中,三目运算符返回的是一个变量的值,没有内存空间,不可以修改。

    int a = 10; int b = 20;  a > b ? a : b;        (a > b ? a : b) = 30(不可以这样操作,原因如上);

    C++语言中,三目运算符返回的是一个变量本身,具有内存空间,可以修改。

    int a = 10; int b = 20;  a > b ? a : b;        (a > b ? a : b) = 30(可以这样操作,原因如上)

 

7.当一个变量有内存空间时(只要在堆栈区),就可以通过它的地址修改内存里的值

 

8.const 的使用

    如:C语言中,const 修饰的变量是一个只读变量,具有内存。

        C++语言中,const 修饰的为常量不具有内存(有额外情况,下面有),它放在符号表中。

 

    1)C++ 中两种情况不一样    

        const int a = 10;        此时 a 不具有内存,(用一个常量初始化)它放在符号表中。所以不可以对它进行修改。

        int b = 9; const int a = b;    此时a 具有内存(在栈区),因为对它进行赋值的是一个变量b(栈区),可以通过指针间接赋值改变 a 的值。

        (上面这种情况为用一个变量初始化 const 变量,所以它可以修改)。

 

    2)在局部变量中。( C语言 和 C++ 语言这种情况不一样)

        C++语言中: const int a = 9; int *p = (int *)&a; *p = 99; 

                此时当对 const 常量 a 取地址,编译器会为它分配地址(在栈区);但是 a 常量从符号表中取值,不可被修改。

                *p 修改的只是分配的内存空间的值。打印结果: a = 9; *p = 99;

        C语言中,变量 a 可以被修改。

 

    3)在全局变量( C语言 和 C++ 语言这两种情况一样)

    extern const int a = 9;     int *p = (int *)&a; *p = 99;(第一种)

    const int a = 9(它放在全局变量);  int *p = (int *)&a; *p = 99(第二种);

    此时对 const 变量 a 取地址或声明 extern 时,编译器会为它分配地址(在只读变量区);这段代码可以编译通过,但是运行阶段会报错,不

    以被运行。 

 

    4)C语言中 const 修饰的变量为外部链接(和普通变量一样)。

        C++语言中,const 修饰的变量为内部链接(和普通变量不一样)。

        不管是C语言还是C++ 语言,普通变量都是外部链接。

 

    5)const 与 #define 的不同, const 有类型,有作用域,可进行编译器类型安全检查。

        #define 无类型,不重视作用域(从定义开始到文件结尾),不可以进行安全检查。

 

9.extern 的使用

    适用范围是不同文件之间变量的调用。(不可以再同一文件不同函数之间使用)

    在不同文件之间调用一个外部链接的变量时,只用在调用的文件中写一个 extern 关键字就可以了。

    当调用一个内部链接时,需要在被调文件和调用文件中都写 extern 关键字。

 

10.变量可以定义数组    int n = 9; int arr[n];

    :不同编译器编译环境效果不同。在vs2013中不可以通过;在QT,linux中可以通过。支持C99的编译器可以通过。

 

11.using 编译指令 与 using 声明的区别

    namespace A{ int a = 9; func(){} }

    namespace B{int a = 9; func(){} }

using 声明: int main(){using A::a; cout << a <<endl; int a = 6;}当第二个 a 被定义时,函数编译通不过,命名冲突

using 编译指令:int main(){using namespace A; cout <<a<<endl; int a = 6; cout << a << endl;} 打印结果为:9,  6; 

        此时打印最近定义的那个。 

        int main(){ using namespace A; using namespace B; cout << a << endl; } 此时就会报错。编译器不知道选择哪个a

注意:使用using声明或using编译指令会增加命名冲突的可能性。也就是说,如果有名称空间,在代码中

        使用作用域解析运算符,则不会出现二义性。        

    

12.在 C 语言中,重复定义多个同名的全局变量是合法的(不正规),但在C++ 中是不合法的。

 

 

"天命不足畏,祖宗不足法,人言不足恤"

第二天 C++基础班 ************************************=============================================

 

1.变量都是有内存空间的,当它在堆栈区时可以通过指针修改它的值。

 

2.引用:本质地址传递;编译器帮忙做了基本地址传递的部分。

    int &b = a;=====> int * const b = &a;(实质)    b常指针,指向不可以改变。

    (本质上:引用是一个常指针)

    1)同一个内存块可以取多个别名。int& b = a; int& c = a;

    2)引用的基本语法:int a = 10;    int &b = a;(引用) b = 100; cout << a << endl; cout << b << endl;

        输出的结果都为 100.

    3)指针的引用; int *p = NULL; int *&p1 = p;  p1 = (int *)malloc(sizeof(int));

    4)数组的引用:(两种方式)

        1. typedef int ARR[10];/*建立数组类型*/ int a[10];/*创建一个数组*/; ARR &p = a;/*对数组的引用*/    p[i] = i;/*对数组的某个元素赋值*/

        2.int (&p1)[10] = a;/*直接建立数组类型,进行引用,并初始化。*/    p1[i] = i; /*对数组的某个元素赋值*/

    "注意内容:" 

    5)    引用没有定义,是一种关系型声明。声明它和原有某一变量(实体)的关系。故 而类型与原类型保持一致,且不

        分配内存。与被引用的变量有相同的地址

        声明引用变量时必须初始化,    int& b; //错误

        必须确保引用是和一块合法的内存块关联( NULL 不可内存不可引用)。

        可以建立数组引用。

    6)引用一旦初始化,不能改变。(原因如上:本质)

        int &b = a;     b = c (这种写法并不是改变b的指向,是将c的值赋给 b; b并没有指向c);

    7)(常量引用)const int &b = a;   b的值、指向都不能改变。

        因为它等同于: const int *const b = &a;(这种写法常用来保护"实参"不受"形参"的改变).

    8)C++编译器在编译过程中使用"常指针"作为引用的内部实现,因此引用所占用的空间大小与指针相同。(非官方的说法,但是大家都这么说)

    9)

        //建立普通变量的引用

        int ma = 9;            

        int &ra = ma;        //建立引用。 ===》 int *const ra = &ma;    即 ra = &ma;

        ra = 88;            //通过引用修改变量的值。这步是编译器帮忙进行解引用,然后赋值的。

        

        //建立对指针的引用

        int *p2 = NULL;        

        int *&mp2 = p2;        //建立引用。===》 int ** const mp2 = &p2;

        mp2 = (int *)malloc(sizeof(int));    //通过引用给指针p2分配空间。也就是给p2重新赋值,让他重新指向。

 

        //建立对数组的引用

        typedef int Arr[10]; /*建立数组类型 */   Arr a;    //建立一个普通数组

        Arr &p3 = a;    //建立引用 ===》 Arr * const p3 = &a;

        p3[3] = 10;   //对数组的第四个元素赋值。

 

3.引用的几点基本知识:

    1)单独定义引用时,必须初始化;说明它很像一个常量,因为常亮在定义时也必须初始化(const int a = 5)。

    2)    普通引用有自己的空间。(在32位平台下占4个字节。)但是引用变量的地址和初始化它的变量是同一块地址。

        int &a = b;    a 和 b 的地址相同。()

struct teacher {int a; char b; int &d; double &c; };        这个结构体所占内存为16;

struct teacher {int a; char b; };        这个结构体所占内存为8;

    3)引用的本质是一个常量指针。

    4)

 

3.函数中的引用:引用做函数的参数,引用做函数的返回值

    1)引用做参数不需要初始化

    2)不能返回局部变量引用;(和返回局部指针变量原因一样)。

    3)引用做返回值。(可以做左值和右值)

    4)指针的引用。

    5)(常量引用):const 对引用的使用(如上:)。const 引用的值不能修改(主要用在函数的形参:不想用形参改变实参的值)

    6)"函数的返回值当左值需要返回引用。"

 

4.类:

    1)使用class关键字

    2)类里面可以放变量、函数。

    3)public: 访问权限

    4)

 

5.内联函数:C++中使用(既有宏函数的效率,又没有普通函数的开销;可以像普通函数那样,进行参数,返回值类型的安全检查,又可以

    作为成员函数。        在C++中,定义内联函数,只是对编译器的一个建议,并不一定会成为内联函数。

(内联函数的语法)

    1)普通函数;  inline void func(int x){  return; }但是必须注意必须函数体和声明结合在一起,否则编译器将它作为普通函数来对待。 

    2)要求:不能存在任何形式的循环语句;不能存在过多的条件判断语句;    函数体不能过于庞大;    不能对函数进行取址操作

 

6.宏函数:C语言中使用

    1) #define      ADD(X, Y)  X+Y

        int main() {int ret = ADD(10,20); return 0;}

    2)副作用很多:无脑替换,不检查语法;没有作用域,从定义开始到文件结束

 

 

7.函数的默认参数和占位参数

    1)默认参数:int func(int x = 10,int y = 20);此时形参的赋值就是默认参数,当函数调用不传参数时,就将使用默认参数。。

    2)注意:

        int func051(int x, int y = 0, int z = 0);//函数的默认参数从左向右,如果一个参数设置了默认参数,那么这个参数之后的参数都必须设置默认参数。

        函数的声明和函数的定义不能同时写默认参数,("即使默认参数相同也不行")编译器不知道该选择哪套

    3)占位参数:

    "函数的占位参数也是参数,必须要给个值,只是函数内部用不了而已. "  int func(int , int y);或 int func(int ,int y = 3);

    当占位参数与默认参数结合时:int func(int =5, int y);"erro" ;这种写法,int (int y, int =30);占位参数只能写在最后一个形参的位置。

    而且可以不给它传参。因为它有了默认参数。

 

8.函数重载:

    1)函数重载的条件:

    "可以作为条件的:"

    同一个作用域:函数名相同,形参的个数、形参的类型、形参的类型顺序不同; 

    用 const 进行修饰的函数也可以进行重载。 非 const 对象优先调用非 const 函数。

    const 对象只能调用 const 函数,const 函数只能调用 const 函数,可以被 const 函数和非 const 函数调用。

    注意:不可以作为条件的

          "函数的返回值不能作为函数重载的条件"

          "函数重载和默认参数不能同时出现";函数重载碰到默认参数,那么要考虑是否会出现函数调用二义性(会报错,编译通不过)。

    2)重载函数的调用:

    正常调用:"函数调用正常匹配函数形参(可以找到)。"

    隐式类型转换后调用:"当找不到匹配的形参时,编译器会进行隐式转换,仍然找不到后会进行报错。(如下)"

     void func(char b); int main(){ int a = 3; func(a);}  此时就会进行隐式转换,因为与ASCII码匹配。

     

    3)函数重载的原理:

        编译器为了实现函数重载,在编译的时候做了一些优化,用不同的类型来修饰不同的函数名。

        如:void func(){}

            void func(int a){}

            void func(int a,char b){}

        上述三个函数编译完后:生成的函数名为:_z4funcv    "v代表void,无参数"

                                              _z4funci   "i代表参数为int类型"

                                              _z4funcic      "i代表第一个参数为int类型,第二个参数为char 类型"

 

 

"天命不足畏,祖宗不足法,人言不足恤"

第三天 C++基础班 ************************************=============================================

 

1.struct 的区别(C 和 C++)

    C语言中只能定义变量。

    C++语言中可以定义变量和函数。同时C++语言中,struct 中所有变量和函数都是 "public" 权限

2.类的封装:

3.类内部的三种权限

    public:共有属性(修饰的成员变量和方法; 可以在类的内部和外部使用。)

    private:私有属性(修饰的成员变量和方法,只能在类的内部使用,不能在类的外部使用)

    protected:主要用于继承,保护属性(修饰的成员变量和方法; 可以在类的内部和继承的子类使用,不能在类的外部使用)

4.struct 和 class 的区别:

    struct 中成员的默认权限为 public;

    class  中成员的默认权限为 private;

5.类的调用(一个类调另一个类)

6.对象的构造和析构;

    1)构造函数:"名称和类名相同";"没有返回值 "。"可以有多个,进行函数重载"

                在内存开辟之后调用构造函数。

 

    2)无参构造函数和有参构造函数(又分为两种)

        1.无参构造函数:定义对象的时候,对象后不能加括号。如:class stu{ stu(){"无参构造函数"}};  stu st;(正确定义对象)。

                    如果加上括号: stu st(); 编译的时候不会报错,(因为编译器把它当作函数声明),运行的时候会报错。

        2.有参构造函数:

            "普通参数":    根据形参的类型和个数,也可以进行函数重载。

class Animal{

    public:    

        Animal(int age)

        {

        cout << "一个参数构造函数数字!" << endl;

        mName = "undefined";

        mAge = age;

        }

        Animal(string name)

       {

            cout << "一个参数构造函数字母!" << endl;

            mName = name;

            mAge = 0;

        }

        Animal(string name,int age)

       {

            cout << "两个参数构造函数!" << endl;

            mName = name;

            mAge = age;

        }

        //拷贝(复制)构造函数  用一个对象来初始化另一个对象

        Animal(const Animal& animal)

        {    //拷贝构造函数,形参必须是引用。否则会造成死循环,一直调用它本身。本质上成了递归调用本身。

            cout << "拷贝构造函数!" << endl;

            mName = animal.mName;

            mAge = animal.mAge;

        }

            "拷贝参数":用一个对象来初始化另一个对象

            它的形参必须是引用。如果是普通变量,就会造成本质上的递归调用本身;无意义。

 

    3)析构函数:"名称=类名+"~""; "没有返回值";"没有参数"。"一个类只可以有一个析构函数"

        在内存释放之前调用析构函数。

7.构造函数调用规则:  (参考上面的代码)

    1) 括号法;

    Animal animal1("smith");    //一个参数构造函数字母!

    Animal animal2(20);            //一个参数构造函数数字!

    Animal animal3("John",30);    //两个参数构造函数!

    Animal animal4(animal3);    //拷贝构造

    2)显示调用构造函数(匿名对象调用拷贝构造的问题)

    Animal("Smith",30);         //匿名函数生命周期仅限于当前行;此行运行完后立即析构。

    Animal animal5 = Animal(30);    //    一个参数构造函数数字!;形参为普通变量

    Animal animal5 = Animal("smith",90);    注意:/"两个参数构造函数!;拷贝构造。这行比上一行多了一个拷贝构造,是因为string容器的缘故。只有这时才会有拷贝构造"

    Animal animal5(animal(30));        //一个参数构造函数数字!。

    Animal animal5(animal(30,"smith"));    "两个参数构造函数!;拷贝构造。"    原因同上,当不调用 string 参数时,它不会调用拷贝构造。"是由编译器处理的,具体处理方式不清楚"

    "上面那两种情况,调不调用拷贝构造函数,主要看参数类型。===有string:就调拷贝构造函数;==== 没有string: 就不调拷贝构造函数===="

    匿名对象如果有变量来接,它就是一个对象;"此时这个变量就相当于匿名对象的名称。"

    匿名对象如果没有变量来接,他就是一个实例化对象。

    Animal4("john",92); Animal(animal4);    此时当这两句在一起,编译器会报错,重定义。

    3)等号法

    Animal animal5 = 10;    //不常用。调用一个函数构造函数

    Animal animal5 = animal4l;    “拷贝构造,常用

8.拷贝构造常用的两种调用方式:

    Animal animal5 = animal4l;

    Animal animal5(animal4l);

 

9.拷贝构造的调用时机:

    1)对象以值传递的方式传递给函数参数。    void func(Animal animal2){ };    用实参初始化形参,调用了拷贝构造函数

    2)用一个对象初始化另一个对象。Animal animal5 = animal4l;  或者:Animal animal5(animal4l);

    3)函数返回局部对象。(注意这里:debug模式 和release模式不一样)。

    debug模式下:会调用拷贝构造,打印两个地址也不相同。

    release模式下,不会调用拷贝构造,打印的地址相同,编译器这里做了优化,直接把原来的对象返回回去了。

    

    Animal MyBussiness()

    {

    //局部对象

    Animal animal;            //无参构造

    cout << &animal << endl;

    return animal;        //编译器先拷贝一份,然后返回。相当于返回一个匿名对象

   }

   void test03()

   {

    Animal animal = MyBussiness();        

    cout << &animal << endl;

   }

 

10.构造函数调用规则:

    默认情况下,编译器至少为我们写的类增加三个函数

    1)默认构造函数(无参,函数体为空)

        默认析构函数(无参,函数体为空)

        默认拷贝构造函数,对类中非静态成员属性简单 "值"拷贝。

    2) 如果用户定义了"拷贝构造函数",C++不会再提供"任何默认构造函数"。

    3)如果用户定义了"普通构造,"C++"不会"再提供"默认无参构造",但是"会提供默认拷贝构造"。

    4)在构造函数中调用构造函数是一个危险的行为。本质上:里面的构造函数相当于一个"匿名对象"。生命周期只有本行(没有变量接的情况下)。

 

11.初始化列表:初始化成员列表只能在构造函数使用。

     "构造函数的初始化列表顺序:"

         1)当一个类中组合了其他类的对象,先执行被组合的构造函数。

         2)如果组合对象有多个,来自不同的类,根据定义的顺序进行构造函数的调用,而不是初始化列表的顺序。

         3)“被组合对象的构造顺序:与定义顺序有关系,与初始化列表的顺序,没有关系”

         4)若类成员中有 const 修饰,必须在对象初始化的时候,给 const 元素初始化。此时就用到了"初始化列表"。

         

         "析构函数:与构造函数的调用顺序相反。"

 

12.深拷贝和浅拷贝

class Person

{

public:

    Person(int a, int b) :_b(b),_a(b){}            "初始化列表"

    "深拷贝"

    Person(const Person &p1)

    {

        mstr = (char *)malloc(strlen(p1.mstr) + 1);        "核心步骤"        “会进行内存拷贝”

        strcpy(mstr, p1.mstr);

        _a = p1._a;

        _b = p1._b;

    }

 

    "浅拷贝"

    Person(const Person &p1)

    {

        mstr = p1.mstr;        "这里只会把一个对象的指针地址,赋给另一个对象的指针。";“不会进行内存拷贝”

        _a = p1._a;

        _b = p1._b;

    }

    void print_func()

    {

        cout << _a <<"   " << _b << endl;

    }

private:

    int _a;

    int _b;

    char *mstr;

};

 

13.C++中 string 是一个类。

 

14.已知一个Print类,在该类的main()函数里执行语句 "Print a, b[2], *c;" 后;程序会自动调用该类的构造函数 4 次。

    a会调用一次;b会调用两次;*c是空指针,没有开辟空间不会调用。

 

15.

 

"天命不足畏,祖宗不足法,人言不足恤"

第四天C++ ++++++=======================================================================================

 

1.explicit:修饰构造函数; 禁止编译器隐式转换

    1)针对单参数的构造函数  2)或者除第一个参数外其他参数都有默认值的多参构造函数。

 

2. C++ 中动态分配内存:

    new :在堆上分配内存。分配对象的堆内存时会调用构造函数

    申请string类型的空间只能string类型数据,可改为string *ps = new string(“10”);

    delete:释放堆内存,在释放堆内存前会调用析构函数。

    delete void*指针,不会调用析构函数。

 

3.创建动态数组:

    int * arr = new int[10];    创建一个 int 类型数组,元素10个。

    delete [] arr;    释放内存。

4.创建自定义对象数组,必须提供无参构造函数。

    同时数组有几个元素就调用几次"无参构造函数和析构函数"。

    delete void* 指针可能会出错。没有调用析构函数

class print

{

private:

    static int a;        //在类中声明静态变量

public:

 

};

int print::a = 0;        //在类外初始化静态变量

 

5.静态成员变量:

    1)在编译阶段就分配空间。对象还没有创建

    2)必须在类中声明,在类外初始化(或定义)。

    3)归同一个类的所有对象所有,共享同一个静态变量。在为对象分配的空间中不包含静态成员所占空间

    4)有权限限制:private静态变量  和 public静态变量。

        private静态变量;在类外不能访问和操作。    类外访问:print::a;"erro";    类外操作:print::a = 6;"erro"

        public静态变量;在类外可以访问和操作。    类外访问:print::a;"yes";    类外操作:print::a = 6;"yes"

    5)const 静态成员变量(const static int a = 8);

        定义静态变量 const 静态成员变量时,最好在类的内部初始化。

 

"静态变量的调用:"

    用类名加域作用符: print::a;

    用对象名加取址符: class p1;     p1.a;

 

6.静态成员函数:(主要是为了访问静态变量)

    与静态变量一样,在没有创建对象前即可通过类名调用。

    1).静态成员函数 只能访问 "静态成员变量",不能访问普通成员变量。

    2).静态成员函数的使用规则和静态成员变量一样

    3)静态成员函数也有使用权限。类外无法访问私有静态函数。

    4)普通成员函数可访问"静态成员变量",也可访问"非静态成员变量"

 

 

 

7.单例模式:一个类只存在一个对象。

    条件:1)构造函数、拷贝函数、析构函数私有化。

          2)提供私有化静态成员变量指向一个对象

          3)提供外部获得对象的静态成员函数。

    class person

    {

    public:

        void print_func(string content)

        {

            cout << "打印内容" << content << endl; 

            mcount++;

            cout << "打印次数" << mcount << endl;

        }

        static person *getInstance(){        //静态成员函数 “在公共区域”,提供接口

            return p1;

        }

    private:

        person(){                    //私有化构造函数

            mcount = 0;

        }        

        person(const person &){}    //私有化拷贝函数

        ~person(){}                    //私有化析构函数

    private:

        static person * p1;            //私有化静态成员变量

         int mcount;

    };    

person *person::p = new person;    (类外初始化静态变量)//私有化的静态成员变量指向一个对象

 

int main(void)

{

    person *p1 = person::getInstance();        p1和p2的地址一样。

    person *p2 = person::getInstance();        因为他们为指向同一个对象。

 

    p1->print_func("晚上好!!!");

    p2->print_func("加油,胜利就在眼前!!!");

 

    return 0;

}

 

8.C++ 成员变量和函数的存储:(成员变量和函数分开存储)

    1)静态变量不在类对象中存储

    2)函数不在类对象中存储(静态函数与非静态函数都是)

    3)类对象中只存储 普通成员变量。

 

9.this 指针:(因为C++成员的存储特点。)

    1)this指针永远指向当前对象。

    2)"静态成员函数" 内部没有 this 指针,所以静态成员函数不能操作非静态成员变量。

  "this 指针的使用:"

  1)当形参与成员变量同名时,可以通过this指针区分。

  2)在类的非静态成员函数中返回对象本身。可以使用:return *this;

class person

{

public:

    person(int a, string name)

    {

        this->a = a;

        this->name = name;

    }

    person func()

    {

        return *this;

    }

 

public:

    int a;

    string name;

};

 

10.空对象指针访问成员的不同情况:由于( this 指针的存在)

    1)当不访问成员变量的时候是允许的。

    2)当访问成员变量时,函数通过this指针寻找成员变量,此时就会报错。

 

class ARE

{

public:

    void print(){

        cout << "hello world !!1" << endl;

    }

    void get_func(){

        cout << ma << endl;

    }

 

public:

    int ma;

};

 

int main(void)

{

    ARE *a1 = NULL;

    a1->print();        "可以访问,因为此时不访问成员变量"

    a1->get_func();    "不可以访问,它队成员变量进行了访问"

    return 0;

}

 

10.const 对成员函数的修饰。(int func() const {});注意:const 要写在小括号后面,大括号前面。(===》int func(const ARE * const this ))

    1)const 修饰 this 指针指向的内存区域。成员函数体内不可改变类中任何变量,除非变量前面用 mutable 进行修饰。

    mutable 对成员变量的修饰

    1)用它修饰后,任何情况下都可以对变量进行修改。

11.const 修饰对象(常对象)。(不允许修改对象的属性)

    1)常对象可访问 const 或非 const 变量,不能修改变量。除非变量前用 mutable 修饰。

    2)常对象只能调用 const 修饰的常函数。

 

 

12.友元函数。(全局函数和成员函数两种)

    1).friend 关键字只出现在声明处。

    2).其他类、类成员函数、全局函数都可声明为友元。

    3)友元函数不是类的成员,不带this指针。

    4)友元函数可访问对象的任意成员属性,包括私有属性。

 

注意:注意:    

    1)友元关系不能被继承

    2)友元关系是单向的,类A是类B的友元,但类B不一定是类A的友元。

    3)友元关系不具有传递性。类B是类A的友元,类C是类B的友元,但类C不一定是类A的友元。

 

#include <iostream>

#include <string>

using namespace std;

class COp2;        "注意:这里必须对第二个类进行声明"

class Myp1

{

public:

    void printf(COp2 &);        "这个类中的函数对另一个的成员变量进行操作"

};

 

class COp2

{

    friend void print_func(COp2 &c1);        "全局函数 友元函数的声明"

    friend void Myp1::printf(COp2 &);        "类成员函数 友元函数的声明。注意这里只能声明"

    friend class Myp1;        "友元类的声明,此类中的所有函数都可以对声明的类中的变量进行访问"

public :

    COp2(int a, string name)

    {

        this->a = a;

        this->name = name;

    }

private:

    int a;

    string name;

};

 

void Myp1::printf(COp2 & c1)        "成员函数的 友元函数的实现""

{

    c1.a += 1;

    cout << c1.a << endl;

    cout << c1.name << endl;

}

 

void print_func(COp2 &c1)        "全局函数的 友元函数的实现"

{

    cout << c1.a << endl;

    cout << c1.name << endl;

}

void test()

{

    COp2 c1(30,"bei jing");

    Myp1 m1;

    m1.printf(c1);

}

 

int main(void)

{

    COp2 c1(30, "bei jing");

    Myp1 m1;

    m1.printf(c1);

    system("pause");

    return 0;

}

 

 

第五天C++==================================++++++++++++++++++++++++++++++++++++++++=

 

1.友元函数案例:

    1)电视机的状态

    

    2)遥控调试

 

 

2.数组案例:

    1)分文件( int  类型的数组)

    2)

 

3.操作符重载:

    1)本质:"函数的调用"。

        函数的参数取决于两个因素:

            1)运算符是一元还是二元。

            2)运算符被定义为全局函数还是成员函数。

 

    2)运算符重载的限制:

        1)使用运算符重载不能改变预算符的优先级,不能改变运算符的参数个数。

        1)不能重载的运算符:"."; "::"; ".*"; "? :"; "sizeof".

        2)除"="号外,基类中重载的运算符都将被派生类继承。

    3)特殊运算符:

        1)“=”; “[]”; “()”;  “->”;操作符只能通过成员函数重载

        2)“<<”;  “>>”;     只能进行友元函数重载

        3)不要重载 “&&”; “||” ; 操作符,因为无法实现短路原则。 

    4.运算符重载建议:

        1)所有的一元运算符,          建议使用:成员函数

        2)“=”;“[]”;“”;“->”;   必须使用成员函数

        3)+=,-=,*=,&=,!=,%=,^=,    建议使用:成员函数

        4)其他二元运算符                建议使用:非成员函数

    5.重载"="号操作符的步骤

        1)先释放旧内存

        2)返回一个引用,实现链式编程。

 

考试天==================================================================================

1.断言函数;  assert(终止进程,抛出异常)   头文件:<assert.h>

2. strcpy 返回值为目标内存的地址;是为了实现链式编程。

    int n = strlen(strcpy(des,src));    链式编程。

3.

 

 

C++第六天========================================++++++++++++++++++++++++++++++++++++++++++++++++++=

"运算符重载"

1.前置++;前置--;

2.后置++;后置--;

    要创建一个临时变量接收计算时的那个变量,记住要把临时变量返回去。

3.== 和 != 号的重载。

 

4.继承:

    1)C++中的继承方式(public, private, protected)会影响子类的对外访问属性。判断某一句话,能否被访问

    (三看原则 )

        1)看调用语句,这句话写在子类的内部、还是外部

        2)看子类如何从父类继承(public, private, protected)

        3)看父类中的访问级别(public, private, protected);

 

    2)继承的使用情况:

        1) protected 继承,只能在家族内部使用,不能在类的外部使用

        2)项目开发中,通常都是 public 继承。

 

5.继承的类型兼容原则:

    1)子类对象可以当作父类对象使用

    2)子类对象可以直接赋值给父类对象

    3)子类对象可以直接初始化父类对象

    4)父类指针可以直接指向子类对象

    5)父类引用可以直接引用子类对象。

 

6.继承与组合混搭情况下,构造和析构函数调用原则

    1) 先调用父类构造函数,再调用组合类构造函数,最后调用自己的构造函数

    2)先调用自己的析构函数,再调用组合类析构函数,最后调用父类析构函数,

    3)先构造的对象后释放

 

7.不可以被继承的函数

    1)基类中的构造函数和析构函数

    2)基类中重载的运算符 = 号函数

 

8.继承中同名成员函数和变量的问题:

    1)如果子类和父类中有相同的函数和变量名;子类调用时默认调用自身的函数和变量。

    2)如果想调用父类的函数和变量,需要加上 "类名和域作用域符",显式调用。

 

9.继承中的静态成员特性:

    1)基类中定义的静态成员,可被所有派生类共享

    2)"静态成员的访问权限和普通成员一样";遵守派生类的访问控制

    3)与函数重载一样,如果子类中重新定义了同名的静态函数,基类的函数将被隐藏

    4)静态成员函数不能是虚函数。

    

9.继承中的函数重载现象;

    1)"如果重新定义了基类中的重载函数,"只要函数名相同,继承的基类中的重载函数将被隐藏。

        想要调用必须使用域作用符显式调用。

 

9.多继承的概念:

    1)多继承可能会出现二义性; 解决办法:显示的调用不同类中的同名属性或方法

    2)多继承的相同数据重复继承。解决办法:虚继承:virtual 既保持一份数据,也不会产生二义性。

 663 10.菱形继承解决办法:

    加上 virtual ,用虚继承。

11.多态 :分为:静态多态(编译时多态)和动态多态(运行时多态)。

面向对象编程的设计思路:"开闭原则(对修改关闭,对扩展开放。)"

    C++的多态性是通过虚函数来实现的。(虚函数允许子类重新定义父类成员,这种做法叫做重写或覆盖)。

12.向上类型转换

 

13.虚函数:函数前加 virtual 修饰

    1)virtual 关键字只能修饰成员函数

    2)构造函数不能为虚函数

    3)创建一个虚成员函数,只需在函数声明前加上 virtual 关键字。

    4)如果一个函数在基类中被声明为 virtual ,那么它在所有派生类中都是 virtual ;它派生类的函数前可以加也可以不加 virtual

    关键字;推荐加。

 

   纯虚函数:virtual int func() = 0; 

   1)当一个类中有纯虚函数时,这个类为抽象类;"同时注意:不能实例化一个抽象类,编译时会报错"。

   2)当继承一个抽象类时,必须实现所有的纯虚函数,否则抽象类派生的类也是一个抽象类。

 

14.建立公共接口的目的:

    1)将子类中公共的操作抽象出来。可以通过一个公共接口操作呢一组类。这个公共接口不需要事先实现,即创建一个公共类。

 

15.模板方法:就是通过创建一个公共借口来实现的。

 

 

 

"==========================================================================================="

一.多态的思想:

    1.封装:突破C函数的概念,。。。用类做函数参数的时候,可以调用对象的属性和对象的方法

    2.继承:A B代码可以复用,可以使用前人的代码

    3.多态。可以使用未来的代码,架构不用改变

二.    多态成立的条件:

    1.要有继承

    2.要有虚函数重写

    3.用父类指针或父类引用 指向子类对象

 

"==========================================================================================="

 

C++第七天========================================++++++++++++++++++++++++++++++++++++++++++++++++++=

 

1.虚析构函数的特点和目的:

    1)通过父类指针,把所有子类对象的析构函数都执行一边。

    2)通过父类指针,释放所有的子类资源

 

2.函数的重写、重载、重定义

    1)重载:

        必须在同一个类中

        重载是在编译期间根据 参数类型和个数决定函数调用的

        子类无法重载父类的函数,父类同名函数将被覆盖

 

    2)重写:

        必须发生在父类与子类之间。(同时分为两类)

        并且父类与子类的函数有完全相同的原型。

        1)virtual 具有这个关键字的,发生重写时会产生多态。

        2)无这个关键字的,叫重定义。

    3)重定义:(非虚函数重写叫重定义)

        父类与子类的有完全相同的函数原型,而且无 virtual 关键字修饰,此时叫做函数重定义。

 

3.子类中的 vptr 指针的分步初始化

    1)当创建子类对象时,如果它具有 vptr 指针, 那么此时是分步初始化的。

    2)当执行父类的构造函数时,子类的 vptr 指针会指向父类的虚函数表

        当父类的构造函数执行完毕后,会把子类的 vptr 指针指向子类的虚函数表

    3)结论:子类的vptr指针是分步初始化的。它在程序运行时会进行寻址操作,效率会降低。不建议将每个成员函数都声明为虚函数。

 

4.纯虚析构函数:

 

5.多态的案例:

    1)思路:

        1.定义一套自己的数据接口,打包放在结构体中,数据接口使用函数指针。

        2.初始化模块接口。(将自己定义的接口与生产商实现的函数联系在一起。)

        3.

 

 

6.函数指针

 

 

 

C++第八天================================================++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

1.C++的模板(用于实现泛型编程)

    1)模板关键字: template<class T,class T1>或 template<typename T, typename T1>    T,T1代表数据类型;

    2)定义的模板关键字当前行下一个函数或类有用。

    3)分为 函数模板 和 类模板;

    4)一旦声明了多个类型 T ,不管用或不用,都必须给它指定类型。

 

2.函数模板的编译机制:(原理)

    1)函数模板具有自动推导功能

    2)编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

    3)函数模板通过具体的类型产生不同的函数。

 

3.模板函数与普通函数的区别

    1)函数模板不允许自动类型转换,必须严格匹配。

    2)普通函数能自动进行类型转换

    template<class T> 

    T ptint(T a, T b ){ }

 

    int show(int a,int b){ }

    void main(){ int a = 6; char b = 'c';  print(a,b);"error",两个变量类型必须相同,不允许进行自动转换    

                  show(a, b);"YES"   “普通函数能自动进行类型转换”};

 

4.函数模板和普通函数一起调用规则

    1)函数模板可以向普通函数一样被重载

    2)如果函数模板与普通函数一样,C++编译器优先调用普通函数。

    例如:    template<class T> 

            void print(T a, T b ){ }

            void print(int a, int b){ }

            void  main(){ int a = 4; int b = 9; print(a, b); "此时优先调用普通函数"}

    3)如果函数模板能产生更好的匹配,则优先调用函数模板。

    例如:template<class T,class T2> 

            void print(T a, T2 b ){ }

            void print(int a, int b){ }

            void  main(){ int a = 4; char b = 'u'; print(a, b); "此时优先调用函数模板"}

    4)可以通过空模板实参列表的语法限定编译器只能调用模板函数

        例如:(此目录第二个函数例子)

            void main(){ int a = 4; int b = 6; print<>(a, b); }

4.类模板:

    1)只能显示的指定类型,不具有自动推导功能。

    2)类模板做函数形参(必须显示指定模板类型)

    3)

 

5.类模板与函数模板(有自动推导功能)的混合使用案例,有奇效。

template <class T1,class T2>

class Person

{

public:

    Person(T1 name, T2 age)

    {

        this->mName = name;

        this->mAge = age;

    }

public:

    T1 mName;

    T2 mAge;

};

//类模板做函数参数(指定类型)

void Dobusiness(Person<string, int> &p1)

{

    cout << "name:" << p1.mName << " age:" << p1.mAge << endl;

}

//类模板与函数模板混合使用(使用函数模板的自动推导功能)

template <class T1, class T2>

void Dobusiness02(Person<T1, T2> &p1)

{

    cout << "name:" << p1.mName << " age:" << p1.mAge << endl;

 

}

void test04()

{

    Person<string, int > p1("jonh", 30);

    Dobusiness(p1);

 

    Person<string, int > p2("jonheqeq", 303);

    Dobusiness02(p2);

}

 

6.类模板派生普通类和类模板类:

    1)继承的话,应该继承一个具体的类,因为一个具体的类,编译器才知道分配多大的内存。

    2)如果想继承类模板,应该给继承过程中的类模板显示指定类型。

    例如(类模板派生普通类)

        template <class T>

        class MyClass{  private:     "父类模板"

                        T age;  };

        class SubClass : public Mycalss<int>{    "普通类,这里继承时,必须对类模板显示指定类型,编译器才知道给父类分配多大的内存"    

            public:

                int a;

        };

    例如(类模板派生子类模板)

        template <class T>

        class MyClass{  private:     "父类模板"

                        T age;  };

        template <class T>            

        class SubClass : public Mycalss<T>{    "子类类模板,这里继承时,可以用子类的类型指定父类的模板"    

            public:

                T a;

        };

        

 

7.类模板分文件编写

    C++文件的编译过程:先每个文件独立编译,然后由链接器将编译好的文件链接在一起(这一步就是寻找各种调用函数,和头文件的定义内容)

    需要引入 .cpp 文件,原因:

        1)C++编译机制的原因;(分文件编译导致的)

        2)二次编译有关

    解决办法文件,将他们放入:    

        类模板的声明与定义要放在一个文件中编写。创建一个.hpp将他们放入

 

 

"C++的类型转换"

 

8.静态类型转换:static_cast<>;

    1)用于类层次结构中基类和派生类之间"指针或引用"的转换

        1)上行转换:把派生类的指针或引用转换成基类的是安全的    Animal *ani = static_cast<Animal*>( dog ) ;

        2)下行转换:把基类的指针或引用转换成派生类的是不安全的,因为没有动态类型检查。    Dog *dog = static_cast<Dog *>(ani);

        3)总结一句就是:可以把大的转成小的; 不能把小的转成大的。

    

    2)当两个类之间无继承关系时,不可以进行转换。

     如:Teacher *t1 = static_cast<Teacher *>(dog);  "error,因为两个类之间无关系"

 

    3)基础数据类型之间的转换:

        1)可以把高精度的类型转换成低精度的;double da; int a = static_cast<int>(da);

        2)当把低

        精度的类型转换成高精度时,安全性需要程序员保证。(原理和上一样)    double da = static_cast<double>(a);

 

9.动态类型转换:dynamic_cast<>(类与类之间的转换)

    dynamic_cast<> 具有类型检查功能,比 static_cast<>类型转换安全。

    1)只能转换具有父子关系的"指针或引用".

        上行转换:只能将子类指针转换成父类指针(可以大转小)

        下行转换:不能将父类指针转换成子类指针(不能小转大);

    例如: parent *parent = dynamic_cast<parent*>son;

        

10.数组模板:

    "1)自定义类型做元素构建数组时,必须提供无参构造函数。"

 

 

C++第九天================================================++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

1.常量转换:(const_cast<>)用来修改类型的 const 属性。

    1)常量指针、引用 被转换成 非常量指针、引用,并且仍然指向原来的对象()互相转换,将非常量转换为常量。

    例如u:

    2)不能直接对非指针、非引用的变量直接使用 const_cast<> 操作符去移除它的 const 属性。

 

2.重新解释转换或强势类型转换。reinterpret_cast<>(不安全的转换,各种类型之间的强转)

 

3.异常的处理(需要了解 C 的异常处理 和C++的处理)

    C的异常处理的缺陷。和特点

 

4.C++的异常特点

    1)异常不能被忽略,而且异常可以跨函数,异常并不是简单的 int  型数字,最好有明确的意义。

    2)C++提供的异常机制,具有跨函数和不可忽略的特点。

    3)异常捕获的严格类型匹配。

 

 

5.栈解旋:异常被抛出后,从进入 try 起,到异常处理完毕,这期间在栈上创建的所有对象都将会自动析构;析构的顺序与创建的顺序相反。

 

6.异常接口声明:

    1)可以在函数前声明抛出的异常类型,如果不声明,表示可以抛出各种类型的异常。

    例如: void func() throw(int , double, char){ }可以抛出三种类型的异常。

            void func() throw(){ }不可以抛出任何类型的异常。

            void func() { }    可以抛出任何类型的异常。

    2)"但是:"

    C++的异常规范在不同编译器下可能执行效果会不同。(编译器的不同会导致结果不同)

    VS下会忽略C++的异常规范,但是会发出警告,程序照常执行;     QT会报错,终止程序运行。    

    通常情况下:如果一个函数抛出了它的异常接口声明所不允许抛出的类型,unexcepted 函数将被调用,该函数默认调用 terminate 函数中断程序。

 

7.异常变量的声明周期

    1)异常也可以抛出一个类的对象。此时可以使用纯虚函数做接口,声明每种不同的异常对象(减少代码量)。

    例如: class BaseException{

    public:    

        virtual void print_exception() = 0;

    };

    class TargetException : public BaseException{

    public:

        virtual void print_exception(){

            cout<<"目标 空间空指针异常" <<endl;

        }

    };

    class DestException : public BaseException{

    public:

        virtual void print_Exception(){

            cout<<"源空间的空指针异常"  << endl;

        }

    };

    void CopyString(char *dest, const char *source){

        if(dest == NULL){

            throw TargetException();

         }

         if(source == NULL){

             throw DestException();

        }

        memcpy(TargetException,DestException,strlen(source)+1 );

     }

     int  main(){

         const char *source = "nnakew";

         char dest[1024] = {0};

         try{

             CopyString(dest, source);

         }catch(BaseException& ex )

         {

             ex.print_exception();

         }

         return 0;

     }

 

 

 

8.C++标准异常类:系统头文件 <exception>

 

9.创建自己的异常类:

    引入系统头文件,继承系统的标准 出错类;重载父类的 what 函数和虚析构函数。

 

10.标准输入输出操作

    cin 是由缓冲区获取文件到 目标内存或变量,若缓冲区没有内容时,才需要从键盘上输入

    cout 是有缓冲区输出文件到屏幕的,若缓冲区没有满时,或强制释放,它是不会在屏幕上输出内容的。

    endl 的作用是刷新缓冲区,将内容输出到屏幕或文件里。

 

10.格式化输入输出操作

 

 

 

C++第十天(STL课程阶段)================================================++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

1.STL六大组件

 

 

2.STL三大组件:容器、算法、迭代器

 

    每一个容器都有自己的迭代器,迭代器用来遍历容器中的元素。

    遍历:不重复的进行访问每个元素。

 

3.容器,迭代器,算法的初步使用。

    1)先引入所需要的容器头文件和算法头文件

 

    void Myprint( int val){         算法中的回调函数,需要自己写,

        cout<< val <<" ";            内容为自己所想打印的内容

    }

    void test(){

    vector<int > V;

    for (int i = 0; i < 5; i++)

    {

        V.push_back(i+ 10);

    }

    vector<int>::iterator pBegin = V.begin();

    vector<int>::iterator pEnd = V.end();

    

    //直接使用迭代器进行容器的遍历

    while (pBegin != pEnd)

    {

        cout << *pBegin << " ";

        pBegin++;

    }

    cout << endl;

 

    //模拟算法的最后一个回调函数的使用

    pBegin = V.begin();

    while (pBegin != pEnd)

    {

        Myprint(*pBegin);

        pBegin++;

    }

    cout << endl;

 

    //使用算法遍历容器,并且打印出来。注意:算法的最后一个参数为回调函数,需要自己写(所想要打印的内从容)

    for_each(V.begin(), V.end(), Myprint);

    cout << endl;

    }

    };

 

    2)容器的分类:序列式容器和关联式容器

        序列式容器:容器元素在容器中的位置由元素进入容器的时间和地点来决定。如:(vector,deque , list, stack, queue )

        关联式容器:容器具有自己的规则,元素在容器中的位置由容器的规则来定。如(树状容器: set/mutilset; map/mutilmap )

    3)每个容器都有自己的迭代器,由容器自己提供。

        

3.string 容器:

    1)从 const char * 到 string 有隐式转换;反之则没有。

 

3.下标操作符: [] 和 at()方法。

    两个的区别在于:1)[] 越界程序会直接挂掉,  2)at()方法越界程序会提供错误信息。

 

4.vector 容器:

    1)连续的内存空间

    2)单口

    3)会实现内存空间动态增长(当已有内存占满时);申请更大的空间,原来的空间析构,原来的迭代器会失效。

    4)它的内存空间不会随着 clear()方法清除数据而消失的,只有当容器生命周期结束时,它的容量才会释放;

        "所以当想在生命周期存在时减少或增大容器内存,可以使用 swap() 方法。"(如下)

void test04(){

 

    vector<int> v;

    for (int i = 0; i < 100000; i++){

    v.push_back(i);

    }

 

    cout << "capacity:" << v.capacity() << endl;

    cout << "size:" << v.size() << endl;

 

    v.resize(3);

    cout << "capacity:" << v.capacity() << endl;            "注意容量与长度不一样,"

    cout << "size:" << v.size() << endl;

 

    //收缩内存

    //vector<int>(v).swap(v);

 

    vector<int>(v).swap(v); //匿名对象

 

    cout << "capacity:" << v.capacity() << endl;

    cout << "size:" << v.size() << endl;

}

    5)reserve的使用:"预留空间",在此空间还没有初始化时是不允许访问。可以通过push_back()插入元素后,进行访问。

    6)resize的使用:开辟空间并且初始化,它的空间申请完后是可以访问的。

    重新指定容器的长度为num,若容器变长,则默认值或指定值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除,"只是长度变短,但是容器的容量没有变化,"。

    7)只要容器的容量产生变化,原来的迭代器就会失效。(如删除元素之后,原来的迭代器就不能用了)。

 

5.迭代器的注意事项:

    1)"(当内存由于动态增长,更换地址后,原来的迭代器就失效了,不可以再使用)。"

    2)只要能遍历容器中所有元素的,都是容器认可的迭代器。

    3)每个容器的迭代器不一样,有自己的独立迭代器。

    4) const_iterator 只读

        reverse_iterator 逆序迭代器

        iterator 最普通的正向迭代器

    5)

6.迭代器的种类:5种。

    1)每个容器只提供一种

    2)都是由对应的容器自己提供的迭代器。(因为每个容器的实现机制不一样)。

    3)输入迭代器,输出迭代器,前向迭代器,双向迭代器,随机访问迭代器

 

 

6.vector 申请和释放空间的注意:

    尽量不要频繁申请和释放空间(太浪费时间);可以先预测一下所用空间的大小,直接用reserve()申请足够大的空间,在逐步初始化。

 

 

7.随机迭代器:(vector容器支持随机访问)

 

8.deque 容器:

    1)是一种双向开口,动态以分段连续内存空间组合而成,随时可以增加一块新内存的容量器。

    2)可以在首尾两端分别作元素的删除和插入操作。

    3)

 

 

C++第十一天(STL课程阶段)================================================++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

1.栈容器(stack) :元素先进后出,单口容器

    1)不提供迭代器;没有遍历功能

    2)以其他的容器作为底层,它相当于提供接口

    3)只有栈顶元素才可以被外界取用。所以想遍历容器,只能从栈顶开始,取出一个元素,就删除它,当遍历完后,容器也就没有元素了。

 

2.队列容器(queue):元素先进先出

    1)没有迭代器。不支持随机访问

    2)两个出口,一个只进,一个只出。

    3)和 stack 栈容器一样,遍历完后,容器的元素也被删除空。

 

3.( stack 和 queue 容器叫受限的线性表)

 

3.链表容器(list):双向循环链表

    1)采用动态存储分配,不会造成内存浪费和溢出。

    2)元素的插入和删除十分方便,修改指针即可。

    3)list容器提供自己的排序算法;

    4)list容器的排序(重点在回调函数的使用)

    5)swap()既可以交换两个链表的数据,也可以动态的伸缩内存。

 

    

 

4.set容器(红黑树,平衡二叉树的一种)

    1)它的元素既是键值又是实值,而且所有的元素都会自动排序。

    2)不允许两个元素有相同的键值。

    3)不允许通过迭代器修改元素的值。

    4)与list有某些相同的性质,当对容器中的元素进行插入和删除操作时,操作之前的所有迭代器,在完成操作完成后都是有效的,除了被删除的那个元素的迭代器

 

5.multiset容器:

    1)特性和set一样,唯一区别在于它允许键值重复。

 

6.算法的默认排序原则:都是由小到大;如果想从大到小,就需要自己建立一个回调函数

 

7.对组( pair ):    将一对值组合成一个值,这一对值可以具有不同的数据类型。两个值可以分别用对组的公有属性 first 和 second 访问。

    创建对组的两种方式:1)pair<string ,int> pair1("xiaobai",20);    cout<< pair1.first <<endl;    cout<< pair1.second <<endl;

    2)pair<string, int> pair2 = make_pair("xiaohong",23); cout<< pair2.first <<endl;  cout<< pair2.second <<endl;

    3)pair<string, int> pair3 = pair2;    cout<< pair3.first <<endl;  cout<< pair3.second <<endl;

 

8.map 容器:(它的所有元素都是一个对组)

    1)键值对;键值不可以相同,实值可以相同。所有的元素都会根据键值自动排序。

    2)插入元素的四种方式:map<int , int> m;

    m.insert(pair<int, int>(1, 2));

    m.insert(make_pair(2, 2));

    m.insert(map<int, int>::value_type(3, 2));

    m[4] = 4;

 

    3)如果通过[] 访问一个不存在的key值,那么编译器会创建一个,实值为默认值。

    4)map的迭代器与普通容器不同。是一个对组迭代器。可以通过迭代器修改实值的值,不可以修改键值的值。

    5)map的迭代器与list迭代器有某些相同的性质,对容器元素进行插入和删除时,操作之前的所有迭代器在操作完成之后不会失效,当然那个被删除的元素迭代器除外。

    6)指定map 的排序规则:(因为它的元素为对组,所以排序规则需要自己写函数确定)

 

9.multimap容器:

    与map 容器操作类似,唯一不同在于它的键值可以相同。

 

10.如何判断容器支持随机访问(或提供随机迭代器):

    只需要看容器提供的迭代器能否 +2,+3 

    vector<int>::iterator it = v.begin();    it = it+3;(可以,提供随机迭代器)

    queue<int>::iterator it = q.begin();  it = it+2;(不可以向后跳跃,不提供随机迭代器)。

 

11.STL中,所有的拷贝都是值寓意,所提供的内容必须是可以拷贝的。

    1)此时就涉及到深拷贝和浅拷贝的问题;当由指针存在,而且它指向堆内存时,用容器提供的拷贝只会复制指针的指向,并没有拷贝到指针所指向的数据。

        当生命周期结束,进行空间析构时,就会出现同一块内存二次析构,程序挂掉。

    2)必须自己重载一个深拷贝函数(和类的函数重载一样;(一般都是:一个拷贝函数和一个重载=号操作函数)。

 

12.STL使用 的时机

 

C++第十二天(STL课程阶段)================================================++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

1.容器中的 find()函数默认区分大小写,而且返回值为迭代器.

 

2.仿函数:重载函数调用操作符的类,其对象成为函数对象,也叫仿函数。

    1)仿函数是一个类,不是一个函数。重载了()

    2)仿函数重载了()操作符,使得函数对象可以像函数一样使用。

    3)重载的operator()需要一个参数的,这样的类对象称为 "一元仿函数"; 依次类推。

 

3.预定义函数对象:(实现了数据类型与算法的分离。)

    1)plus<int> p; 预定义好的函数对象,能实现不同类型数据的 + 运算。

    2)

 

3.函数没有类型,不能定义变量。函数对象有类型,因为他是一个类。

 

4.函数对象与普通函数的区别:(02)

    1)函数对象超出了普通函数的概念,可以保存函数的调用状态

    2)函数对象可以做参数和返回值

    

5.谓词:

    1)普通函数或重载的operator()"返回值为bool 类型"的函数对象(仿函数)。

    2)一个参数的叫一元谓词,以此类推。

6.兰博打函数表达式:    vector<int> v1;

    for_each(v1.begin(), v1.end(), [](int val){ cout<< val<<" "; });

    for_each(v1.begin(), v1.end(), [](int val)->void{ cout<< val<<" "; });

 

7.内建函数对象

/*

template<class T> T plus<T>//加法仿函数

template<class T> T minus<T>//减法仿函数

template<class T> T multiplies<T>//乘法仿函数

template<class T> T divides<T>//除法仿函数

template<class T> T modulus<T>//取模仿函数

template<class T> T negate<T>//取反仿函数

*/

7.函数对象适配器:

    1). 让自己编写的函数对象继承基类,如果是一元函数对象需要继承unary_function,

    如果是二元函数对象继承binary_function

    2). 函数对象operator()函数后增加 const

    3). 使用bind2nd  bind1st(将一个二元函数对象转变为一元函数对象),

        区别为: bind1st: 将参数绑定为函数对象的第一个参数    bind2nd: 将参数绑定为函数的第二个参数。

    

    4)for_each():遍历打印容器内容。返回值为最后一个函数对象的类型。

    

    6)取反适配器

    find_if():查找容器中有无所寻找的内容,返回值为查找类型的迭代器。

    对一元函数对象、谓词取反用 not1; 对二元函数对象、谓词取反用 not2;

 

    7)给普通函数绑定参数(需要:先把一个普通的函数指针适配成函数对象)

        ptr_fun函数指针适配器:把一个普通函数指针适配为函数对象。

        void Myprint(int val1, int val2){  cout<< val1 + val2 <<" ";  }

        void main() { for_each(v1.begin(), v1.end(), bind2nd(ptr_fun(Myprint), 200));  }

    8)成员函数适配器(mem_fun_ref:容器中存放的为对象实体。)(mem_fun:容器中存放的为对象指针。)

        vector<person> v1;   person p1("aaa", 10);    v1.push_back(p1);

        for_each(v1.begin(), v1.end(), mem_fun_ref(&person::showperosn));

        vector<person*> v1;  v1.push_back(&p1);

        for_each(v1.begin(), v1.end(), mem_fun(&person::showperosn));

 

 

8.算法:

    遍历算法:

        1)for_each算法:第三个参数,接受的函数类型。

        2)transform;

 

    查找算法

        1)find(查找元素或对象) 与 find_if(查找指针所指向的内容)

        2)adjacent_find查找相邻重复元素

        3)binary_search(二分查找法),需要查找的容器元素有序排列

        4)count 与 count_if (统计容器中元素的个数)

 

    排序算法

        1)合并两个有序序列(merge)两个序列必须是有序的。且顺序应该一样(或从小大,或从大到小,两种方式参数有点区别,默认从小到大,从大到小需要额外添加函数对象)

        2) sort算法(条件:容器必须支持随机访问)

        3)random_shuffle(打乱容器中的顺序)    

        4)reverse (反转)

        5)

    拷贝和替换算法

        1)copy (拷贝)

        2)replace(替换)把所有要替换的值都替换成目标值。

        3)replace_if

        4) swap

 

    算数生成算法

        1)accumulate (累加容器中元素)

        2)fill(填充)

        3)

    集合算法

        1)set_intersection (求交集)    注意它的返回值。

        2)set_union (求并集)  

        3)set_difference (差集);

 

 

C++第十三天(数据结构、算法课程阶段)================================================++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

1.线性表:分类:

 

    1)线性表顺序存储:

 

    2)线性表线性存储:

        1)单向链表

        2)单向链表(企业链表)

        3)循环链表(企业链表单向)

        4)解决约瑟夫问题(用单项循环链表)

        5)双向链表

        6)受限的线性表

            1)栈的顺序存储( stack )

            2)栈的链式存储( stack )

            3)队列的顺序存储( queue )

            4)队列的链式存储( queue )

            5)栈的应用(1.就近匹配,用来测试编码的括号使用规范)

            6)栈的应用(2.中缀表达式转后缀表达式)

            7)(计算机)基于后缀表达式的计算;

            

    

3.树的特点:

    1)非线性结构,有 1 个直接前驱,但可能有多个直接后继(1 :n);

    2)树的定义具有递归性,树中还有树。

    3)树可以为空,即节点为0。

 

4.树的一些专业名称

    1)节点的度:节点挂接的子树数(即一个节点有几个直接后继就有几度)。

    2)节点的层次:即根到该节点的层数(根节点算一层)

    3)树的度:所有 节点的度 中的最大值

    4)树的深度(或高度):指所有节点中的最大层数。

    5)节点数:一棵树中的所有节点个数(包括根节点)。

    6)叶子节点:即终端节点,没有后继。

 

5 二叉树

    1)二叉树的基本特征:(1 :2)

        每个节点最多只有两个子树

        左子树和右子树的位置不能颠倒

    2)二叉树的性质:

        1)在二叉树的第 i 层上,最多只有2^(i-1)个节点。(i > 0)

        2)深度为 k 的二叉树,最多只有 2^k -1 个节点。(k>0)

        3)对于任何一个二叉树,若度为2 的节点有n2个,则它的叶子数必为 n2+1个。

        4)满二叉树:深度为K,有 2^K-1 个节点。特点是:每层都充满了节点。

6.完全二叉树:

    1)除最后一层外,每层的节点数都达到了最大值,且最后一层的节点尽力靠左。

    2)对于完全二叉树:若从上至下,从左至右编号,则编号为i的节点,其左孩子编号为 2i, 右孩子编号为 2i+1, 双亲的编号为 i/2;(i等于1时,为根除外)。

    1)左孩子右兄弟:可以将一颗多叉树转变为二叉树

    2)递归遍历(周游)二叉树

    3)非递归遍历二叉树(用栈的方法)

    4)遍历的三种方法:

        1)先序:先根再左再右

        2)中序:先左再根再右

        3)后序:先左再右再根

    5)

        1)已知二叉树的"先序"遍历和"后序"遍历序列"不能"唯一地确定这这棵树。

        2)已知二叉树的"先序"遍历和"中序"遍历"可以"唯一地确定这这棵树。

            1)先根据先序找到根节点,在根据中序确定根节点左右两边的节点

            2)再根据先序找到下一个根节点,再根据中序找到根节点左右两边的节点。

        3)"中序"、"后序"可以确定一棵树

        4)总体结论为:带"中序"的都可以确定一棵树,反之则确定不了

 

    6)三种遍历的特点:

        1)先序遍历:可以确定每棵树的根节点。(特点: 它的输出序列中:第一个数为树的总根节点;剩下的子根节点,和中序遍历可以确定)

        2)中序遍历:可以确定根节点的左右子树。(它的特点为:输出序列中,根节点两边的为它的左右子树,和先序遍历配合可以确定一棵树)

        3)后续遍历:(它的特点;输出序列中,最后一个数为为树的总根节点。)

 

C++第十四天(数据结构、算法课程阶段)================================================++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

1. 求二叉树叶子节点的数目(用全局变量写一遍)

    可以用静态变量和全局变量,还有形参参数。

2. 求二叉树的高度(深度)

 

3.拷贝二叉树:

 

4.释放二叉树的节点。

 

5.井号法创建树。

 

6.排序算法

    1)新型冒泡排序(加标识量)

    2)新型选择排序(加标识量)

    3)插入排序:

        1.构建有序和无序序列,2.无序序列插入到有序序列中。

        2.应用场景:序列基本有序,元素较少。

        3.两步操作:元素拿出来,符合条件的元素后移。

    4)希尔排序:

        要点:增量:(len / 3 + 1);进行分组

        

        缺点: 排序不稳定,相同大小的几个元素,排列完后前后位置可能会发生变化。

        2, 3, 4, 5, 5, 6, 5; 这几个5排序完后,可能与现在的前后顺序不同。  

 

    5)快排:

            要点:基准数(选取)

            一轮遍历下来,基准数的插入位置左边部分都比他小,右边部分都比它大。

 

    6)归并排序:(将两个有序序列合并成一个有序序列)

        要点:1)先分组,将数据最后分成一个元素为一组的有序数列

             2)再两两合并排序; 

             3)需要一个与本来元素相同大小的辅助空间。

 

    7)堆排序:

        大顶堆:所有的根节点元素都比子节点两个元素大(完全二叉树)。

        小顶堆:………都比子节点元素小。

        1)先找出最后一个子根节点(len/2-1: len为数据的个数 。)

        2)从这个子根节点开始对堆进行数·初始化,形成(大顶堆或小顶堆)

        3)对初始化后的堆进行从第一个节点对最后一个节点交换(两两对应),每次交换完后,再对堆进行调整,(因为堆的结构可能发生变化)。

 

    8)等号= 对排序效率的影响。

 

"C++实现链表:"

    1)*的链表,宝哥的链表;有头节点,无头节点链表;

    2)与C做对比。

 

考试:

一. 类中三种变量的初始化:

    1.static 静态成员变量时:          (正确写法:在类外初始化:类型 类名::成员名)

    2.const 成员初始化;        (正规写法:在类内用初始化列表初始化)

    3.const static 成员初始化:    (正确写法:本地初始化;在类内定义的地方初始化)

    class person{

    public:

        person(int num = 0) : Age(10)      "初始化列表"

        { }    

        void show()

        {

            cout<<"Age:"<<Age<<" ID"<<ID<<" size"<<size<<endl;

        }

    private:

        static int ID;                    "在类外初始化"

        const int Age;                    "初始化列表"

        const static int size = 100;    "在本地初始化:即定义的地方"

    };

    int person::ID = 30;

 

二.容器打印的三种方式

    1.回调函数

    2.回调对象

    3.兰博打表达式

    "================================="

    4.C++11 的表达式:

    for(int val : arr){            "其中arr是一个数组。V是一个vector容器"

        v.push_back(val);

    }

    5.STL 求和表达式的第三个参数:累加初始值,容器的所有元素的和加上这个值为最后的求出值。

 

    6.谓词;count_if() 的使用。

    7.绑定适配器:

 

三.用栈(两个)实现队列

    1.可以在一个类里面包含两个栈,用来实现队列的方式。当用这个类定义对象时,他就是一个队列。

    2.有意思;但是没想出来,两种方法做一遍,存博客

 

四.用归并的思想合并链表

    1.

    五.迭代器的理解:

    1.erase()函数的返回值,它的迭代器在循环遍历中的奇特之处;

    2.循环遍历,it++放置不同的位置;

      1)在正常的for循环位置

      1)符合条件时erase()容器中的某个元素,但是没有接返回值

      2)符合条件时erase()容器中的某个元素,接了返回值;

          3)符合条件时,接了返回值,同时在下面接着 it--;看容器首位元素符合条件和不符合条件的两种情况。

              2)不放在正常位置

              1)放在不符合条件的情况下it++;

相关标签: C++基础