C++ Learning

image-20201218201354063

1. C++数据类型

​ 1. 基本内置类型:bool、char、int、 float、double、void、wchar_t

​ 其中wchar_t(宽字符型)是这样来的typedef short int wchar_t;

  1. typedef声明

    typedef type newname使用typedef为一个已经存在类型取一个新的名字。

    1
    2
    typedef int feat;
    feat a = 10;
  1. 枚举类型

    如果一个变量只有几种可能的取值,那么可以定义为枚举类型

    1
    2
    enum color {red, green, blue} c;
    c = blue;

    默认情况下,第一个名称的值为0,第二个名称为1,以此类推。但也可以赋予值。比如:

    1
    enum color {red, green=5, blue};

    在这里,blue的值为6, 但是red仍然为0。只是green后面的值都必须大1。

2. 变量作用域

  1. 在函数内部或一个代码块内部声明的变量,称为局部变量。只在函数内部或者代码块内部有效

  2. 在函数参数的定义中声明的变量,称为形式参数。—形参 形参改变并不会改变实参

  3. 在所有函数外部声明的变量,称为全局变量 在程序的整个生命周期都有效。

    注意:局部变量被定义的时候,系统不会对其初始化。定义全局变量,系统会自动初始化。

image-20201214211148762

3. 定义常量

  • 使用#define预处理器

    ​ 只能定义全局常量

    1
    #define HIGH 10
  • 使用const关键字 (建议使用)

    ​ 可在函数内部定义

    1
    const int HIGH = 10;

4. C++存储类

  • auto存储类

    声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。

    1
    auto f = 3.14;
  • register 存储类

    用于定义存储在寄存器中而不是RAM中的局部变量。寄存器只用于需要快速访问的变量。

    它意味着变量可能存储在寄存器中,取决于硬件和实现的限制。

  • static 存储类

    指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行销毁和创建。

    当修饰全局变量时,会使全局变量的作用域限制在声明它的文件内。

  • extern 存储类

    用来在另一个文件声明一个在第一个文件中已经定义过的全局变量或函数。

  • mutable 存储类

    仅适用于类的对象,允许对象的成员替代常量。也就是说,mutable成员可以通过const成员函数修改。

  • thread_local 存储类

    声明的变量尽可在它在其上创建的线程上访问,变量在创建线程时创建,并在销毁销毁线程时销毁。

5. 循环

- while
- for
- do...while
- break
- continue
- goto(禁用)

6. 判断

  • if

  • if…else

  • switch

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    switch(expression){
    case constant-expression :
    statement(s);
    break; // 可选的
    case constant-expression :
    statement(s);
    break; // 可选的

    // 您可以有任意数量的 case 语句
    default : // 可选的
    statement(s);
    }
    • expression必须是一个整型枚举类型,或者是一个class类型,其中class有一个单一的转换函数将其转为整型或枚举类型。
    • 每个case后跟一个要比较的值和一个冒号。
    • constant-expression必须于expression的数据类型相同,且必须是一个常量字面量
    • 当被测试的变量等于case中的常量时,case后跟的语句将被执行,直到遇到break
    • 遇到break语句,switch终止
    • 如果case语句不包含break,则控制流将会继续后续的case。
    • default case ,出现在switch结尾,如果case都不为真,则执行该case
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
using namespace std;

int main ()
{
// 局部变量声明
char grade = 'D';

switch(grade)
{
case 'A' :
cout << "很棒!" << endl;
break;
case 'B' :
case 'C' :
cout << "做得好" << endl;
break;
case 'D' :
cout << "您通过了" << endl;
break;
case 'F' :
cout << "最好再试一下" << endl;
break;
default :
cout << "无效的成绩" << endl;
}
cout << "您的成绩是 " << grade << endl;

return 0;
}

7. C++函数

​ 函数参数:

  • 传值调用:把参数的实际值赋值给函数的形参, 修改函数内部的形参对实参无影响。

  • 指针调用:把参数的地址赋值给函数的形参,在函数内,该地址用于访问调用中要用到的实际参数,修改形参也会修改实参。

  • 引用调用:把参数的引用赋值给函数的形参,在函数内,该引用用于访问调用中要用到的实际参数,修改形参会修改实参。

    在函数定义的时候可以初始化参数值int sum(int a, int b=20){}

Lambda函数与表达式

[capture](parameters) mutable ->return-type{statement}

  • [capture]:捕捉列表。[]是lambda的引出符。捕捉列表能够捕捉上下文中的变量供lambda函数使用

    1
    2
    3
    4
    5
    6
    []					//没有定义任何变量。使用未定义的变量会引发错误
    [x, &y] //x以传值方式传入,y以引用方式传入
    [&] //任何被使用到的外部变量都隐式地以引用方式传入
    [=] //任何被使用到地外部变量都隐式地以传值方式传入
    [&, x] //x显式地以传值方式加以引用,其余变量以引用地方式
    [=,&z] //z显示地以引用地方式加以引用,其余变量以传值方式加以引用
  • (parameters): 参数列表,与函数i相同,如果不需要传参,可省略

  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性,注意,如果使用该修饰符,那么参数列表则不可以省略,即使参数为空。

  • ->return-type: 返回类型。不需要可省略

  • {statement}: 函数体

    1
    2
    [](int x, int y)-> int {int z=x+y;
    return z+x;}

8. C++数组

​ 声明一个数组

type arrayName [ arraySize];

1
double balance[10];

​ 初始化数组

​ 初始化语句

1
double balance[5] = {100.0, 2.0, 3.4, 4.0, 32.0};

​ 如果省略数组arraySize,则将数组大小初始化为初始化元素个数。

  • 多维数组

    type name[size1][size2]...[sizeN];

    以二维数组为例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #  声明
    int a[3][4];

    # 定义
    int a[3][4] = {
    {0, 1, 2, 3},
    {4, 5, 6, 7},
    {8, 9, 10, 11},
    };

    # 内部嵌套括号可省略
    int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
  • 指向数组的指针

    数组名是指向数组中第一个元素的常量指针。

    double runoobAarray[50];中,runoobAarray是一个指向&runoobAarray[0]的指针。及数组的首元素的地址。

    1
    2
    3
    4
    5
    double *p;
    double runoobAarray[10]={1,2,3,4,5,6,7,8,9,10};
    p = runoobAarray;
    // 打印数组第五个元素
    cout<<*(p+4)<<endl;
  • 传递数组给函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 第一种:形参是一个指针

    void myfunc(int *param){}

    // 第二种:形参是一个已定义大小的数组

    void myfunc(int param[10]){}

    // 第三种:形参是一个未定义大小的数组

    void myfunc(int param[])

    注:如果采用第二种方式,并且传入的实参的长度并不一致,则会截断或补全处理。也就说是,在函数内部param的长度一定是10;

  • 从函数返回数组

    如果想要从函数返回一个一维数组,则必须声明一个返回指针的函数。

    1
    int *myfunc(){}

9. C++字符串

​ C风格字符串

​ 字符串实际上是使用null字符\0终止的一维字符数组

1
2
3
char greeting[6] = {'h','e','l','l','o','\0'};

char greeting[] = "hello";

strcpy(s1, s2); 复制字符串s2到字符串s1
strcat(s1, s2); 链接字符串s2到字符串s1的末尾
strlen(s1); 返回字符串s1的长度
strcmp(s1, s2); 比较字符串
strchr(s1, ch); 返回一个指针,指向字符串s1中字符ch的第一次出现的位置。
strstr(s1, s2); 返回一个指针,指向字符串s1中字符串s2的第一次出现的位置

10. C++指针

每一个变量都有一个内存位置,每一个内存位置都定义了可使用的&运算符访问的地址,它表示了在内存中的一个地址。

​ 什么是指针?

​ 指针是一个变量,其值为另一个变量的地址,即内存位置的直接地址。

​ 指针声明

type *point

  • 指针数组

    int *ptr[MAX];’数组元素为整型指针

  • 指向指针的指针

    一个指针包含一个变量的地址,当我们定义一个指向指针的指针,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。

    image-20201215162339962

int **var;

  • 传递指针给函数

    想要传递指针给函数只需要简单的声明函数参数为指针类型即可。在调用函数的时候,需要传一个地址进去。

  • 从函数返回指针

    允许从函数返回一个指针,必须声明 一个返回指针的函数

    int *myfunc(){}

易混淆:指针常量与常量指针

1
2
3
4
5
6
7
8
9
10
11
12
int a = 94;
int b = 101;
// 常量指针
// const int *p = &a;
int const *p=&a; //const 修饰的是*p所以*p不能改变,但是p可以改变
p = &b;
// 指针常量
int *const q = &a; //const 修饰的是 q 所以q不能改变,但是*q可以改变
*q = 2;
cout << *p << " " << *q << endl;
// 常量指针常量
const int *const de = &a; //都变不了啦

11. 引用

​ 引用变量是一个别名,是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。

​ 引用vs指针:

- 不存在空引用。引用必须连接到一块合法的内存。/
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象
- 引用必须在创建时被初始化。指针可以在任何时间被初始化。
1
2
int i = 17;
int& r = i;

​ C++把引用作为参数:

1
void swap(int& x, int&y){}

​ C++把引用作为返回值:

1
2
3
4
5
6
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0}
double& setValues(int i){
return vals[i];
}

setValues(1) = 20.24

12. C++类&对象

​ 类定义是以关键字class开头,后跟类的名称。类的主体包含在一对花括号中,类定义后必须跟着一个分号或一个声明列表。

1
2
3
4
5
6
7
8
class Bos {
public:
double lenght;
double breadth;
double height;
}; //这tm有个分号,记住了,sb

Bos bos1;

​ 声明类的对象,和声明基本类型变量一样

  • 类成员函数

    类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其它变量一样。类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中所有成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//	类的成员函数可以定义在类内

class Box {
public:
double length;
double breadth;
double height;

double getVolume(void) {
return length*breath*height;
}
}

// 也可以定义在类外面,但先需要在类的内部先声明该函数
// 在类的外部定义需要使用 范围解析运算符 :: 定义该函数

class Box {
public:
double length;
double breadth;
double height;
// 成员函数声明
double getVolume(void);
}

double Box::getvolume(void) {
return length*breadth*height;
}
  • 类访问修饰符

    数据封装是面向对象编程的一个重要特点,它防止函数直接访问类的内部成员。类成员的访问i限制是通过在类的主题内部对各个区域标记public、private、protected来指定的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Base {
    public:
    //共有成员
    protected:
    //受保护成员
    private:
    //私有成员

    };
    • public:

      ​ 公有成员在程序中类的外部是可访问的。

    • private:

      ​ 私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有友元函数可以访问私有成员。默认情况下,类的所有成员都是私有的。

    • protected:

      ​ 受保护的成员变量或函数与私有成员十分相似,但有一点不同,protected成员在派生类中是可访问的。

    相应的也有三种继承方式:

    - public继承:基类public成员、protected成员,private成员的访问属性在派生类中不变。
    - protected继承:基类public成员、protected成员、private成员的访问属性在派生类中分别变成:protected、protected、private
    - private继承:基类public成员、protected成员、private成员的访问属性在派生类中分别变成:private、private、private。

    但无论哪种继承方式:

    - private成员只能被本类成员和友元函数访问,不能被派生类访问
    - protected成员可以被派生类访问。
  • 类构造函数以及析构函数

    类的构造函数会在每次创建类的新对象时执行。构造函数的名称与类的名称完全相同,并且没有返回值(也不返回void)。用于为某些成员变量设置初始值。

    无参的构造函数

    1
    2
    3
    4
    5
    6
    7
    8
    class Line {
    public:
    Line(); //构造函数声明
    }

    Line::Line(){
    cout<<"这是构造函数"<<endl;
    }

    有参的构造函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Line {
    public:
    Line(int a); //构造函数声明
    private:
    int length;
    }

    Line::Line(int a){
    length = a;
    }

    使用初始化列表来初始化字段

    1
    Line::Line(int len): length(len){}

    等价于

    1
    2
    3
    Line::Line(int len){
    length = len;
    }

    类的析构函数,会每次在删除所创建的对象时执行。

    析构函数的名称与类的名称时完全相同的,只是在前面加了破浪号作为前缀,不会返回任何值也能带有参数,析构函数有助于在跳出程序以前释放资源

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Line {
    public:
    Line();
    ~Line(); //析构函数声明
    }

    Line::~Line(){
    cout<<"结束啦"<<endl;
    }
    Line::Line(){
    cout<<"开始啦"<<endl;
    }
    const int* a;
    int * const a;
  • 拷贝构造函数

    它在创建对象时,使用同一类中之前创建的对象来初始化新创建的对象,拷贝构造函数通常用于:

    +  通过使用另一个同类型的对象来初始化新创建的对象。
    +  复制对象把他作为参数传递给函数
    +  复制对象,并从函数返回这个对象。

    如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。

    1
    classname (const classname &obj){}

    obj是一个对象引用,该对象是用于初始化另一个对象的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    #include <iostream>

    using namespace std;

    class Line
    {
    public:
    int getLength( void );
    Line( int len ); // 简单的构造函数
    Line( const Line &obj); // 拷贝构造函数 *****
    ~Line(); // 析构函数

    private:
    int *ptr;
    };

    // 成员函数定义,包括构造函数
    Line::Line(int len)
    {
    cout << "调用构造函数" << endl;
    // 为指针分配内存
    ptr = new int;
    *ptr = len;
    }

    Line::Line(const Line &obj)
    {
    cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
    ptr = new int;
    *ptr = *obj.ptr; // 拷贝值
    }

    Line::~Line(void)
    {
    cout << "释放内存" << endl;
    delete ptr;
    }
    int Line::getLength( void )
    {
    return *ptr;
    }

    void display(Line obj)
    {
    cout << "line 大小 : " << obj.getLength() <<endl;
    }

    // 程序的主函数
    int main( )
    {

    Line line(10);
    cout << "my test" << endl;
    Line res = line; //调用了拷贝构造函数
    cout << "end my test" << endl;
    // line heap
    // obj = line stack


    display(line); //调用了拷贝构造函数

    return 0;
    }
  • 友元函数

    类的友元函数是定义在类外部,但有权访问类的的所有私有成员和保护成员。不属于类的成员函数。

    友元可以是一个函数,也可以是一类,如果是友元类,整个类及其所有成员都是友元。

    使用关键字friend

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Box {
    double width;
    public:
    double length;
    friend void printWidth(Box box); //声明友元函数
    void setWidth(double wid);
    }

    void printWidth(Box box){
    cout << box.width<<endl
    }
  • 内联函数

    内联函数通常与类一起使用,如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。使用关键字inline

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #include <iostream>

    using namespace std;

    inline int Max(int x, int y)
    {
    return (x > y)? x : y;
    }

    // 程序的主函数
    int main( )
    {
    cout << "Max (20,10): " << Max(20,10) << endl;
    cout << "Max (0,200): " << Max(0,200) << endl;
    cout << "Max (100,1010): " << Max(100,1010) << endl;
    return 0;
    }

    内联函数内不允许使用循环语句和开关语句;内联函数的定义必须出现在内联函数的第一次调用之前;类结构中所在的类说明内部定义是内联函数。

  • C++this指针

    在C++中,每一个对象都能通过this指针来访问自己的地址。this指针是所有成员函数的隐含参数,在成员函数内部,可以用this指向调用对象。

    友元函数没有this指针,因为友元函数不是类的成员函数,只有成员函数才有this指针。

  • C++指向类的指针

    访问指向类的指针的成员,需要使用成员访问运算符->

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #include<iostream>
    using namespace std;

    class Box {
    private:
    int length;
    public:
    Box(int len);
    void getLength();
    };

    Box::Box(int len){
    length = len;
    }

    void Box::getLength(){
    cout<<length<<endl;
    }

    int main(){
    Box box(1);
    Box *p = &box; //指向对象的指针
    p->getLength(); //使用成员访问运算符调用成员函数
    }
  • C++类的静态成员

    允许使用static关键字来把成员定义为静态的。当声明类的成员为静态时,无论实例化多少个类,静态成员都只有一个副本。静态成员在所有对象中是共享的。

    如果把成员函数声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存的情况下也能被调用。静态函数只要使用类名加范围解析运算符就能可以访问。静态函数没有this指针,只能访问静态成员以及类外部的函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    #include <iostream>

    using namespace std;

    class Box
    {
    public:
    // 静态成员变量
    static int objectCount;
    // 构造函数定义
    Box(double l=2.0, double b=2.0, double h=2.0)
    {
    cout <<"Constructor called." << endl;
    length = l;
    breadth = b;
    height = h;
    // 每次创建对象时增加 1
    objectCount++;
    }
    double Volume()
    {
    return length * breadth * height;
    }
    // 静态成员函数
    static int getCount()
    {
    return objectCount;
    }
    private:
    double length; // 长度
    double breadth; // 宽度
    double height; // 高度
    };

    // 初始化类 Box 的静态成员
    int Box::objectCount = 0;

    int main(void)
    {
    // 即使没有实例化类,依然可以访问
    // 在创建对象之前输出对象的总数
    cout << "Inital Stage Count: " << Box::getCount() << endl;

    Box Box1(3.3, 1.2, 1.5); // 声明 box1
    Box Box2(8.5, 6.0, 2.0); // 声明 box2

    // 在创建对象之后输出对象的总数
    cout << "Final Stage Count:
    " << Box::getCount() << endl;

    return 0;
    }

13. C++继承

​ 当创建一个类时,可以不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类成为基类,新建的类成为派生类。一个类可以派生自多个类,它可以从多个基类继承数据和函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<iostream>

using namespace std;

// 基类
class Shape {

public:
void setWidth(int w){
width = w;
}
void setHeight(int h){
height = h;
}

int getWidth(){
return width;
}
protected:
int width;
int height;
};

// 派生类
class Rectangle: public Shape{

public:
int getArea(){
return (width * height);
}
};

int main(void){
Rectangle Rect;

Rect.setWidth(5);
Rect.setHeight(10);

// 输出对象的面积
// 继承数据
cout << "Total area: " << Rect.getArea() << endl;
// 输出宽度
// 继承函数
cout << "Width is:" << Rect.getWidth() << endl;

return 0;
}

​ 派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为private

​ 一个派生类继承了所有的基类方法,但下列情况除外:

1. 基类的构造函数、析构函数和拷贝构造函数。
2. 基类的重载运算符。
3. 基类的友元函数。

多继承:即一个子类可以有多个父类,它继承了多个父类的特性。

14. C++重载运算符和重载函数

​ C++允许在同一作用域中的某个函数运算符指定多个定义,分别称为函数重载运算符重载

重载声明是指一个与之前已经存在该作用域内声明过的函数或方法具有相同名称的声明,但是他们的参数列表和定义(实现)不同。

  • 函数重载

    在同一作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形参(个数、类型、啥顺序)必须不同。通过指定返回类型不同来实现重载函数是不被允许的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #include<iostream>
    using namespace std;
    class printData {

    public:
    void print(int i){
    cout << "这是打印整数的函数i" << endl;
    cout << i << endl;
    }

    void print(double i){
    cout << "这是打印浮点数的函数i" << endl;
    cout << i << endl;
    }
    };

    int main() {
    printData pd;
    pd.print(12);
    pd.print(12.2);
    }
  • 运算符重载

    重载的运算符是带有特殊名称的函数,函数名是由关键字operator和其后要重载的运算符符合构成。与其它函数一样,重载元素符由一个返回类型和一个参数列表。

    Box operator+(const Box&);声明加法运算符用于把两个box对象相加,返回最终的Box对象,这条指令是定义为类的成员函数,如果想要定义为类的非成员函数,那么我们需要为每次操作传递俩个参数。Box operator+(const Box&, const Box&);

15. C++多态

​ C++多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<iostream>
using namespace std;

class Shape {
protected:
int width, height;
public:
Shape(int a=0, int b=0){
width = a;
height = b;
}
int area(){
cout << "Parent class area :" << endl;
return 0;
}
};

class Rectangle: public Shape{
public:
// 派生类不能够继承构造函数、析构函数、拷贝构造函数
// 需要重写
Rectangle(int a=0, int b=0): Shape(a, b){}
int area(){
cout << "Rectangle class area :" << endl;
return 0;
}
};

class Triangle: public Shape{
public:
Triangle(int a=0, int b=0): Shape(a, b){}
int area(){
cout << "Triangle class area :" << endl;
return 0;
}
};

int main(){
Shape *shape;
Rectangle rec(10, 7);
Triangle tri(10, 5);

shape = &rec;
shape->area();
shape = &tri;
shape->area();
}

上述程序执行结果:

image-20201216151143611

为什么呢?

这是因为调用函数area()被编译器设置为基类中的版本,这就是所谓的静态多态静态链接–函数调用在程序执行前就准备好了。有时候这也被成为早绑定,因为area()函数在程序编译期间就已经设置好了。

在基类中,area()函数的声明前放置关键字virtual。修改后,程序执行的结果:

image-20201216152214011

引入虚函数:虚函数是在基类中使用关键字virtual声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。

在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被成为动态链接

如果想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是在基类中不能对虚函数给出有意义的实现,这时候引入纯虚函数*

1
2
3
4
5
6
7
8
9
10
11
12
class Shape {
protected:
int width, height;
public:
Shape(int a=0, int b=0){
width = a;
height = b;
}
// 纯虚函数,令其=0,告诉编译器,函数没有主体。
virtual int area() = 0;

};

16. C++数据抽象&数据封装

​ 数据抽象是指只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。

​ 数据封装是面向对象编程中把数据和操作数据的函数绑定在一起的概念,这样能避免收到外界的干扰和误用,从而确保了安全。

数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。

17. C++接口(抽象类)

接口描述了类的行为和功能,而不需要完成类的特定实现。

​ C++接口是使用抽象类来实现的;如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。

1
2
3
4
5
6
7
8
9
class Box{
public:
// 纯虚函数
virtual double getVolume() = 0;
private:
double length;
double breadth;
double height;
}

​ 设计抽象类的目的,是为了给其它类提供一个可以继承的适当的基类。抽象类不能被用于实例化对象,它只能作为接口使用。抽象类的派生类需要被实例化,就必须实现每个虚函数。可用于实例化对象的类被成为具体类

18. C++文件和流

​ 要在C++中进行文件处理,必须添加头文件

​ 其中定义了三个新的数据类型。

数据类型 描述
ofstream 表述输出文件流,用于创建文件并向文件写入信息
ifstream 表示输入文件流,用于从文件读取信息
fstream 表示文件流,同时具有俩种功能

​ 打开文件:

void open(const char *filename, ios::openmode mode);

​ 示例

1
2
3
4
5
6
7
8
9
10
11
12
fstream outfile;
//打开文件
outfile.open("file.dat", ios::out | ios::in);

//写文件
outfile << "写入文件信息" << endl;

//读文件
outfile >> data;

// 关闭函数
outfile.close();

19. C++异常处理

​ throw: 当问题出现时,程序会抛出一个异常,这是通过使用throw关键字来完成的。

​ catch:在您想要处理问题的地方,通过异常处理程序捕获异常。catch关键字用于捕获异常。

​ try:try块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个catch块。

20. C++动态内存

使用new运算符来为任意的数据类型动态分配内存。new data-type。这里data-type

可以是包括数组在内的任意内置的数据类型,也可以是包括类或结构在内的用户自定义的任何数据类型。

1
2
3
4
5
6
7
8
9
10
11
double* pvalue = NULL;
pvalue = new double;

// 如果自由存储区已被用完,可能无法成功分配内存。
// 所以建议检查new运算符是否返回NULL指针

double* pvalue = NULL;
if(!(pvalue = new double)){
cout << "Error: out of memory." << endl;
exit(1);
}

new相比C语言的malloc()函数,主要优点:new不只是分配了内存,它还创建了对象。

在任何时候,当您觉的某个已经动态分配内存的变量不再需要使用时,可以使用delete操作符释放它所占用的内存。

1
delete pvalue;

21. C++命名空间

命名空间这个概念是作为附件信息来区分不同库中相同名称的函数、类、变量等。

命名空间的定义使用关键字namespace,后跟命名空间的名称。

1
2
3
namespace namespace_name{
//代码声明
}

为了调用带有命名空间的函数或变量,需要在前面加上命名空间的名称。

1
namespace_name::code;	//code可以是变量或函数

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<iostream>
using namespace std;

// 第一个命名空间
namespace first_space{
void func();
}

// 第二个命名空间
namespace second_space{
void func();
}

void first_space::func(){
cout << "Inside first_space" << endl;
}

void second_space::func(){
cout << "Inside second_space" << endl;
}

int main(){
first_space::func();
second_space::func();
}

using指令

使用using namespace指令,这样在使用,命名空间时就可以不用在前面加上命名空间的名称。这条指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;

// 第一个命名空间
namespace first_space{
void func(){
cout << "Inside first_space" << endl;
}
}
// 第二个命名空间
namespace second_space{
void func(){
cout << "Inside second_space" << endl;
}
}
using namespace first_space;
int main ()
{

// 调用第一个命名空间中的函数
func();

return 0;
}

22. C++模板

​ 模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。

模板是创建泛型类或函数的蓝图或公式。

模板函数的定义的一般形式

1
template <typename type> return-type func-name(parameter list){}

​ type是函数所使用的数据类型的占位符名称。这个名称可以在函数定义中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<iostream>
#include<string>

using namespace std;

template <typename T> inline T const& Max(T const& a, T const& b){
return a < b ? b : a;
}

int main(){
int i = 39;
int j = 20;
cout << "Max(i, j): " << Max(i, j) << endl;

double f1 = 13.5;
double f2 = 20.7;
cout << "Max(f1, f2): " << Max(f1, f2) << endl;

string s1 = "Hello";
string s2 = "World";
cout << "Max(s1, s2)" << Max(s1, s2) << endl;
}

类模板

1
template <class type> class class-name{}

​ type是占位符类型名称,可以在类被实例化的时候进行指定。可以使用一个逗号分隔的列表来定义多个泛型数据类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include<iostream>
#include<vector>
#include<cstdlib>
#include<string>
#include<stdexcept>

using namespace std;

template <class T> class Stack{
private:
vector<T> elems;
public:
void push(T const &);
void pop();
T top() const; //???
bool empty() const {
return elems.empty();
}
};

template<class T>
void Stack<T>::push(T const& elem){
elems.push_back(elem);
}

template<class T>
void Stack<T>::pop(){
if(elems.empty()){
throw out_of_range("Stack<>::pop(): empty stack");

}
elems.pop_back();
}

template <class T>
T Stack<T>::top() const{
if(elems.empty()){
throw out_of_range("Stack<>::top():empty stack");
}
return elems.back();
}

int main(){
try{
Stack<int> intStack;
Stack<string> stringStack;
intStack.push(7);
cout << intStack.top() << endl;

stringStack.push("hello");
cout << stringStack.top() << std::endl;
stringStack.pop();
stringStack.pop();
}
catch(exception const& ex){
cerr << "Exception:" << ex.what() << endl;
return -1;
}
}

23. C++预处理器

预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理

#开头,不属于C++语句,所以不以分号结尾

  • #define 预处理器

    用于创建符号常量。该符号常量通常称为。指令的一般形式

    #define macro-name replacement-text

    在编译之前替换。

  • 参数宏

    可以使用#define定义一个带有参数的宏

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include<iostream>
    using namespace std;

    #define MIN(a, b) (a<b?a:b)

    int main(){
    int i, j;
    i = 100;
    j = 30;
    cout <<"较小的值为:"<<MIN(i, j)<<endl;
    }
  • 条件编译

    有几个指令可以用来有选择的地对部分程序源代码进行编译。这个过程称为条件编译

    条件预处理器的结构与if结构很像。

    1
    2
    3
    #ifdef NULL
    #define NULL 0
    #endif
    1
    2
    3
    #ifdef DEBUG
    cerr << "Variable x="<<x<<endl;
    #endif

    如果在指令#ifdef DEBUG之前已经定义了符号常量DEBUG,则会对程序中的cerr语句进行编译。

    可以使用#if 0语句注释掉程序的一部分

    1
    2
    3
    #if 0
    不进行编译的代码
    #endif

    示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include<iostream>
    using namespace std;

    int main(){

    #ifdef DEBUG
    cerr << "Trace: Inside main function" << endl;
    #endif
    #define DEBUG
    #if 0
    /* 这是注释部分*/
    cout<<MKSTR(HELLO C++)<<endl;
    #endif

    #ifdef DEBUG
    cerr << "Trace:Coming out of main fuction" << endl;
    #endif
    return 0;
    }

    运行结果

    image-20201217183235300

  • #和##运算符

    #运算符会把 replacement-text令牌转换为用引号引起来的字符串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include<iostream>
    using namespace std;

    // 把x转成字符串
    #define MKSTR(x) #x

    int main(){
    cout << MKSTR(HELLO C++) << endl;
    // 上面那条指令等价于下面这条
    cout << "HELLO C++" << endl;
    return 0;
    }

    ##运算符用于连接俩个令牌

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include<iostream>
    using namespace std;
    #define concat(a, b) a##b

    int main(){
    int xy=100;
    cout << concat(x,y);
    return 0;
    }

24. C++信号处理

信号是由操作系统传给进程的终端,会提早终止一个程序。

​ 有些信号不能被程序捕获,但是下表所列信号可以在程序中捕获,并可以基于信号采取适当的动作。这些信号是定义在C++头文件中。

信号 描述
SIGABRT 程序的异常终止,如调用abort
SIGFPE 错误的算术运算,比如除以0或导致溢出的操作
SIGILL 检测非法指令
SIGINT 程序终止信号
SIGSEGV 非法访问内存
SIGTERM 发送到程序的终止请求

​ C++信号处理库提供了signal函数,用来捕获突发事件,

void (*signal (int sig, void (*func)(int)))(int);

​ 这个函数接收俩个参数:第一个参数是一个整数,代表了信号的编号;第二个参数是一个指向信号处理函数的指针。

​ 待补充…

25. C++多线程

​ 多线程是多任务处理的一种特殊形式。一般情况下,有两种类型的多任务处理:基于进程和基于线程

- 基于进程的多任务处理是程序的并发执行。
- 基于线程的多任务处理是同一程序的片段的并发执行。

​ 后续补充

26. C++标准库

​ C++标准库可以分为两部分:

标准函数库:这个库是由通用得、独立得、不属于任何类得函数组成。函数库继承自C语言。

面向对象类库:这个库是类及其相关函数的集合。

  • 标准函数库:
    • 输入\输出 I/O
    • 字符串和字符处理
    • 数学
    • 时间、日期和本地化
    • 动态分配
    • 其它
    • 宽字符函数
  • 面向对象类库:
    • 标准的C++ I/O类
    • String类
    • 数值类
    • STL容器类
    • STL算法
    • STL函数对象
    • STL分配器
    • 本地化库
    • 异常处理类
    • 杂项支持库
------ 本文结束 ------
0%