c++

c++

命名

文件名结尾为.cpp

编译

使用g++编译

1
g++ learn.cpp -o learn

hello world

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

int main(int argc, char *argv[]){
cout << "hello world! 你好,世界!" << endl;
return 0;
}
  • 主函数必须要返回int
  • endl会立即刷新缓冲区实现换行,\n也可以实现换行,但没有刷新缓冲区的作用,所以无法保证立即输出到屏幕上,只有在执行后面的语句时才会写到屏幕上
  • cout为一个对象

cin

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

int main(void)
{
int age;
char name[12];
cout << "输入姓名,年龄:";
cin >> name >> age;
cout << "姓名:" << name << endl << "年龄:" << age << endl;
}

cin为一个对象,接收标准输入

namespace

c++中需要使用命名空间来区分不同的函数名,变量名等。例如cin,cout输入std这个命名空间

使用namespace的几种方式:

1
2
3
4
// 使用整个std命名空间
using namespace std;

count << "hello";
1
2
// 在需要的对象前面加上命名空间名
std::cout << "hello" << std::endl;
1
2
3
4
5
// 只使用指定的命名空间中的对象
using std::cout;
using std::endl;

cout << "hello" << endl;

总之这些写法都和perl中的使用很相似

struct

c++中,struct在使用的时候不需要写struct(c语言中声明与使用都需要)

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

struct Student{
char name[15];
unsigned int age;
};

int main(int argc, char **argv)
{
Student stu1 = {"昊天", 22};
stu1.age = 24;
cout << stu1.name << stu1.age << endl;
}

new/delete

空间的申请与释放不再用c语言的malloc与free,而是使用new与delete

1
2
3
int *age = new int;
*age = 20;
delete age;

申请时并赋值

1
int *age = new int(20);

申请数组与释放

1
2
3
int *list = new list[5];
memset(list, 0, sizeof(int)*5);//全部初始化为0
delete[] list;

reference

c++中多了引用,之前学perl的时候觉得引用和指针是一样的,但是现在看来这两个存在本质上的区别,引用是多个变量指向同一个地址。指针就是内存地址,引用是对同一块内存地址的不同命名。

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

int main(int argc, char *argv[])
{
int a = 1;
// 整数
int &b = a;
cout << a << endl << b << endl;
}
1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
char a[10] = "hello";
// 字符
char (&b)[10] = a;
cout << a << endl << b << endl;
}
1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
const int a = 1;
// 常量
const int &b = a;
cout << a << endl << b << endl;
}
1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
int a[10];
// 数组
int (&b)[10] = a;
cout << a[0] << endl << b[9] << endl;
}
1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
int a[10][2] = {10};
// 二位数组
int (&b)[10][2] = a;
cout << a[0][1] << endl << b[9][0] << endl;
}
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
int a = 1;
int *p1 = &a;
int *(&p2) = p1;
*p2 = 4;
cout << *p1 << endl << *p2 << endl;
}

修改了其中一个,其它的也会改变,因为都是同一个内存地址。然而并不知道这个引用有什么作用,直到将引用作为函数参数的时候才发现挺有用。因为之前必须使用指针才能修改外部的值,现在用引用也可以做到。

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

// 传参为一个int类型的引用
void num(int &b)
{
b = 1000;
}
int main(int argc, char *argv[])
{
int a = 1;
num(a);
cout << a << endl;
}

引用做为函数返回值

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

int & num(int &b)
{
b = 1000;
return b;
}
int main(int argc, char *argv[])
{
int a = 1;
// 要避免对函数返回的引用做再次引用的操作
int &c = num(a); // 这种写法错误
cout << c << endl << a << endl;
return 0;
}

因为当一个函数运行结束之后,所在空间被释放,那么再对其中的局部变量做引用的话,就是非法操作,所以不能对函数的返回引用再次引用(不能引用局部变量)

引用与指针

  • 引用的时候需要初始化,而指针不需要
  • 引用过得变量不能再使用,而指针可以
  • 引用不占用空间,而指针需要占用空间(指针是数据类型,存储地址)
  • 引用效率高于指针,引用直接操作空间,指针通过地址间接操作
  • 引用更加安全,引用是数值操作,指针可能会有地址偏移
  • 引用灵活性没有指针好

&符号的三种用法区分

  • 在声明变量前,表示引用int &a = b;
  • 在变量前面,表示取地址scanf(int, &a);
  • 在数字中间,表示与运算1 & 2;

函数

函数参数默认值

如果只指定部分值,那么后面的参数必须有指定值,而且需要连续,不能间隔指定

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

void num(int age, int score=0, string name="昊天")
{
cout << "age:" << age << endl << score <<endl << name << endl;
}

int main(void)
{
num(20, 99, "斗罗");
}

函数重载

同一作用域内,函数名相同,但是参数不同(包括参数数量和参数类型)的互相为重载函数

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

void num(int age, int score=0, string name="昊天")
{
cout << "age:" << age << endl << score <<endl << name << endl;
}
void num(int age)
{
cout << "age:" << age << endl;
}

int main(void)
{
num(20, 99, "斗罗");
}

函数重载可以让函数根据参数类型,自动选择对应的函数。

构造函数

对对象的数据进行初始化,在实例化对象的时候自动调用。

  • 构造函数无返回值
  • 构造函数的函数名与类名相同
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;

class Student
{
private:
int age;
public:
// 构造函数
Student()
{
age = 22;
}
void get_age(void)
{
cout << age << endl;
}
};

int main(void)
{
Student stu1;
stu1.get_age();
return 0;
}

如果构造函数需要接收参数,那么在实例化的时候需要传参

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
#include <iostream>
using namespace std;

class Student
{
private:
int age;
public:
// 设置默认值
Student(int a = 22)
{
age = a;
}
void get_age()
{
cout << age << endl;
}
};

int main(void)
{

// 使用默认值时,不需要写括号
Student stu;
stu.get_age();
// 传参则会覆盖默认值,使用括号传参
Student stu1(18);
stu1.get_age();
// 指针对象传参
Student *stu2 = new Student(33);
stu2 -> get_age();
return 0
}

初始化列表

对构造函数的数据进行初始化,而非赋值

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

class Student
{
private:
int age;
public:
// 构造函数对数据进行初始化
Student():age(14) {}
void get_age()
{
cout << age << endl;
}
};

int main(void)
{
Student stu;
stu.get_age();
return 0;
}

析构函数

在对象销毁时自动调用,一般用在销毁指针变量时

  • 不能进行重载
  • 没有参数
  • 函数名为~类名
  • 指针对象需要手动释放,所以在调用delete时,才会自动调用析构函数
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
#include <iostream>
using namespace std;

class Student
{
int age;
public:
// 构造函数
Student():age(20)
{
cout << age << endl;
}
// 析构函数
~Student()
{
cout << "end object" << endl;
}
};

int main(void)
{
// 作用域为括号内,局部对象
{
Student stu1;
}
// 对象被销毁
return 0;
}
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
#include <iostream>
using namespace std;

class Student
{
int age;
public:
Student():age(20)
{
cout << age << endl;
}
~Student()
{
cout << "end object" << endl;
}
};

int main(void)
{
{
Student stu1;
}
Student *stu2 = new Student;
// 指针对象在调用delete时,才会执行析构函数
delete stu2;
return 0;
}

临时对象

没有对象名的对象创建方式(必须加括号),其作用域为当前所在语句。语句执行完,对象会被自动销毁。所以临时对象的析构函数会在语句执行完调用

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

class Student
{
int age;
public:
Student():age(20)
{
cout << age << endl;
}
~Student()
{
cout << "end object" << endl;
}
};

int main(void)
{
Student();
return 0;
}

malloc/free VS new/delete

malloc/free无法自动调用构造函数与析构函数

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

class Student
{
int age;
public:
Student():age(20)
{
cout << age << endl;
}
~Student()
{
cout << "end object" << endl;
}
};

int main(void)
{
// 使用malloc/free时,不会自动调用构造函数/析构函数
Student *stu1 = (Student *)malloc(sizeof(Student));
return 0;
}

this

为系统提供的一个对象指针

虽然this的作用是为了避免同名变量的问题,但是我发现即便是同名的不加this也没有问题,而老师的视频里面不加this是会出问题的,所以现在的c++已经改变了,可以不用加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
#include <iostream>
using namespace std;

class Student
{
public:
int age;
Student(int age)
{
// 使用this来指示对象的变量,而非参数
this -> age = age;
cout << age << endl;
}
~Student()
{
cout << "end object" << endl;
}
};

int main(void)
{
Student stu(22);
return 0;
}

常函数

在函数后添加const的函数为常函数,常函数中不可对变量进行修改

  • 构造函数和析构函数不能为常函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

class Student
{
int age = 20;
public:
// 添加const的函数称为常函数
void set_age(int age) const
{
// 常函数中不可对变量进行赋值
this -> age = age;
cout << this -> age << endl;
}

};

int main(void)
{
Student stu;
stu.set_age(22); // 赋值会报错
return 0;
}

常对象

被const修饰的对象为常对象,常对象只能调用常函数,不能调用普通函数

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 Student
{
int age = 20;
public:
void set_age(int age) const
{
cout << this -> age << endl;
}

};

int main(void)
{
// 常对象只能调用常函数
const Student stu;
stu.set_age(22);
return 0;
}

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
#include <iostream>
using namespace std;

class Student
{
// static成员
static int age;
public:
void set_age() const
{
cout << this -> age << endl;
}

};

// 在类外部进行初始化
int Student::age = 23;

int main(void)
{
const Student stu;
stu.set_age();
return 0;
}

只有静态常量整形才能在类内部进行初始化

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

class Student
{
static const int age = 24;
};

int main(void)
{
Student stu;
return 0;
}

拷贝构造

一个对象以另一个现有对象为基础进行实例化称为拷贝构造

浅拷贝

默认的拷贝构造实现对非静态成员的复制(浅拷贝)

首要条件:拷贝构造函数中传参为局部对象,而且使用引用可以对速度有一定优化

1
2
3
4
5
6
7
8
9
10
Student()
{

}
// 默认拷贝构造,传递参数为一个局部对象Student的引用&variable
// 如果传参为局部对象,那么会多走一步拷贝构造,使用引用则不会,而是使用原来的对象作为参数
Student(const Student&variable)
{

}

拷贝构造的方式:

  • 将现有对象作为参数

    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
    #include <iostream>
    using namespace std;

    class Student
    {
    int age;
    public:
    Student()
    {

    }
    // 实现系统默认的拷贝构造
    Student(const Student&)
    {
    age = 18;
    cout << age << endl;
    }
    };

    int main(void)
    {
    Student stu1;
    Student stu2(stu1); // 1
    Student stu3 = stu1; // 2
    Student stu4 = Student(stu1); // 3
    Student *stu5 = new Student(stu1); // 4
    return 0;
    }
  • 将现有对象作为值

    1
    Student stu3 = stu1;
  • 以一个临时对象作为值

    1
    Student stu4 = Student(stu1);
  • 对象指针中将现有对象作为参数

    1
    Student *stu5 = new Student(stu1);

深拷贝

浅拷贝存在的问题在于指针变量,当构造函数中有指针变量的时候,而且在析构函数中对指针变量进行了释放,那么在调用拷贝构造后,第一次释放指针变量后,释放拷贝构造的指针时,会因为第一次已经将指针变量释放掉了,而造成程序崩溃。所以如果构造函数中如果存在指针变量,那么就需要用深拷贝,深拷贝则是在构造函数中申请一块新的地址,来存储之前的指针所指向的值

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
#include <iostream>
using namespace std;

class Student
{
public:
int *age;
Student()
{
age = new int;
*age = 18;
}
Student(const Student &a)
{
// 拷贝构造申请新的空间
this->age = new int;
// 这里是值的传递,而不是指针的传递
*age = *(a.age); // a.age是一个指针
}
~Student()
{
delete age;
}
};

int main(void)
{
Student stu;
cout << *(stu.age) << endl;
Student stu1 = stu;
cout << *(stu1.age) << endl;
return 0;
}

<cstring>中的memcpy可以将连续数组进行复制,适合数组复制

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
#include <iostream>
#include <cstring>
using namespace std;

class Student
{
public:
int *age;
Student()
{
age = new int[2];
age[0] = 18;
age[1] = 19;
}
Student(const Student &a)
{
this->age = new int[2];
// this->age[0] = a.age[0];
// this->age[1] = a.age[1];
// 当使用数组时,可以使用内存复制,一次性将所有值复制过来
memcpy(this->age, a.age, 8);
}
~Student()
{
delete age;
}
};

int main(void)
{
{
Student stu;
cout << stu.age[0] << endl;
Student stu1 = stu;
cout << stu1.age[1] << endl;
}
return 0;
}

内联函数

在函数名前加inline,编译时会自动将函数调用的地方替换为函数本体,增加了代码区内存消耗,但是提高了执行速度

  • 优点:避免了常规函数的寻址跳转,节省了cpu寻址时间
  • 缺点:代码量增多,让程序内存占用高一些
  • 类内部的普通函数都是内联函数
  • 内联函数如果写在头文件中,那么需要将定义和函数本体都写在里面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
using namespace std;

inline int max_num(int a, int b);

int main(void)
{
int max = max_num(8, 4);
cout << max << endl;
}

inline int max_num(int a, int b)
{
int max = (a > b)?a:b;
return max;
}

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>
#include <string>
using namespace std;

class Student
{
public: //需要有public修饰,否则是private变量,不可访问(类中的函数/对象赋值)
// char name[15];
string name; // 返回类型需要为string,使用不了char
int age;
string get_name(void)
{
return name;
}
};

int main(void)
{
Student stu1;
stu1.name = "昊天";
stu1.age = 20;
cout << stu1.age << endl;
stu1.get_name();
}

不知道为什么,一直使用不了字符数组char?

  • 类名最好大写

  • 使用class标识,struct也是类的一部分

  • 变量具有严格的作用域,使用public修饰的才能被外部使用

  • 除了static变量外,其它的都需要通过对象访问(实例化才会分配空间,类只是一个数据类型)

  • 类的实例化有两种方式

    • 使用普通变量(栈区):class_name object_name
    • 使用指针(堆区),是要使用new关键字:class_name point = new class_name
      • 最后需要使用delete删除堆区变量
      • 必须使用new,否则只是声明了一个指针对象,没有实例化对象
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;

class Student
{
public:
string name;
int age;
string get_name(void)
{
return name;
}
};

int main(void)
{
Student *stu1 = new Student;
stu1 -> name = "昊天";
stu1 -> age = 20;
cout << stu1 -> age << endl;
}

访问修饰符

  • public:类外部可以访问(结构体默认为public)
  • private:仅类内部可以访问,外部不可见
  • protected:仅类内部以及子类可以访问
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;

class Student
{
// private: 默认为private
string name = "li";
int age;
public:
string get_name(void)
{
return name;
}
};

int main(void)
{
Student *stu1 = new Student;
string na = stu1 -> get_name();
cout << na << endl;
}

friend

有元:让被private和protected修饰的变量/函数对指定函数/类可见(修饰friend函数/类)

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
#include <iostream>
using namespace std;

class Student
{
private:
int age;
int get_age(void)
{
return age;
}
// friend修饰的函数可见私有变量/函数
friend void fun();

};

void fun(void)
{
Student stu1;
stu1.age = 22;
stu1.get_age();
}


int main(void)
{
fun();
}

主函数也可被声明为friend函数

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 Student
{
private:
int age;
int get_age(void)
{
return age;
}
friend int main();
};

int main(void)
{
Student stu1;
stu1.age = 22;
stu1.get_age();
return 0;
}

同样的,类也可以被friend修饰,成为friend类,作用同上

运算符重载

一般来说函数是不可以和数字相加的,因为数据类型不一致,但是可以使用运算符重载来达到这个目的,它的主要意义还是对于同一个类的多个对象

类外部运算符重载

1
2
3
4
operator+(class_name_reference, variable)
{

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

class Num
{
public:
int a = 10;
};
void operator+(Num &v, int b)
{
cout << v.a + b << endl;
}

int main(void)
{
Num num1;
num1 + 10;
return 0;
}
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
#include <iostream>
using namespace std;

class Num
{
public:
int a = 10;
};
void operator+(Num &v, int b)
{
cout << v.a + b << endl;
}
// 还可以使用函数重载来实现自动判断参数类型
void operator+(int b, Num &v)
{
cout << v.a + b << endl;
}

int main(void)
{
Num num1;
// 自动判断参数类型
num1 + 10;
20 + num1;
return 0;
}

类内部运算符重载

参数不需要写类,默认左边为类

1
2
3
4
operator+(variable)
{

}
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 Num
{
public:
int a = 10;
int operator+(int b)
{
cout << a + b << endl;
return a+b;
}
};


int main(void)
{
Num num1;
num1 + 10;
return 0;
}

因为默认左边为类的参数,所以不能实现左边为数字,右边为类的这种运算

i/ostream重载

cout是ostream的一个对象

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
#include <iostream>
using namespace std;

class Out
{
int age;
public:
Out()
{
age = 18;
}
// 使用friend可以让外部operator的运算符重载可以使用private变量age
friend ostream & operator<<(ostream &os, const Out &out);
};

// 返回一个ostream的引用
ostream & operator<<(ostream &os, const Out &out)
{
os << out.age ;
return os;
}

int main(void)
{
Out ot1;
// 重载ostream,实现对象的输出
cout << ot1 << " year's old" << endl;
return 0;
}

cin是istream的一个对象

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
#include <iostream>
using namespace std;

class In
{
private:
int age;
friend istream & operator>>(istream &is, In &in);
public:
void get_age()
{
cout << this->age << endl;
}
};

istream & operator>>(istream &is, In &in)
{
// 重载istream,实现输入对对象的赋值
is >> in.age;
// 这里需要调用istream的内置函数fail进行输入错误判断
if(is.fail())
{
in.age = 0;
cout << "input error" << endl;
}
return is;
}

int main(void)
{
In in1;
cin >> in1;
in1.get_age();
return 0;
}

继承

将公共的类作为基类,被子类继承后,子类也会拥有基类的所有属性(成员变量,成员函数)

1
2
3
4
class class_name : permission basic_class_name
{

}

权限修饰符:修饰子类的继承之后的权限

  • public:拥有与父类一致的权限
  • protected:父类的public降低为protected,其它不变
  • private:所有成员变为private
  • 如果不写权限修饰符,那么默认为private

构造函数继承

  • 有参构造:父类的构造函数中有参数,需要通过子类的构造函数的初始化列表进行传递
  • 无参构造:先调用父类的构造函数,再调用子类的构造函数
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;

class School
{
int school_num;
public:
School()
{
school_num = 1;
cout << school_num << endl;
}
};

class Student : public School //继承父类School
{
int stu_num;
public:
Student()
{
stu_num = 1001;
cout << stu_num << endl;
}
};

int main(void)
{
Student stu1;
return 0;
}
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;

class School
{
int school_num;
public:
School(int a)
{
school_num = a;
cout << school_num << endl;
}
};

class Student : public School
{
int stu_num;
public:
Student() : School(1) //通过子类的构造函数的初始化列表对父类的构造函数进行传参
{
stu_num = 1001;
cout << stu_num << endl;
}
};

int main(void)
{
Student stu1;
return 0;
}

析构函数继承

先执行子类的析构,再执行父类的析构,一级一级的向上析构

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
#include <iostream>
using namespace std;

class School
{
int school_num;
public:
School(int a)
{
school_num = a;
cout << school_num << endl;
}
~School()
{
cout << "father 析构" << endl;
}
};

class Student : public School
{
int stu_num;
public:
Student() : School(1)
{
stu_num = 1001;
cout << stu_num << endl;
}
~Student()
{
cout << "son 析构" << endl;
}
};

int main(void)
{
Student stu1;
return 0;
}

1
1001
son 析构
father 析构

同名成员的覆盖

如果子类中有与父类中同名的成员(数据成员,函数成员),那么子类中的成员会对父类的成员进行覆盖(但是也可以使用类名作用域来调用父类成员)

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
#include <iostream>
using namespace std;

class School
{
public:
int s_num = 10;
void get_num()
{
cout << s_num << endl;
}
};

class Student : public School
{
public:
int s_num = 20;
void get_num()
{
cout << s_num << endl; //覆盖
cout << School::s_num << endl; // 通过类名作用域来调用父类数据成员
}
};

int main(void)
{
Student stu1;
stu1.get_num(); // 覆盖
stu1.School::get_num(); // 通过类名作用域来调用父类函数成员
return 0;
}

20
10
10

friend不可继承

父类的friend函数不可被子类继承

static公共

static成员是公共的,都可以使用,但不属于继承的范围

多态

多态:父类的指针,可以有多种执行状态,则被称为多态。

多态是一种范型编程思想,虚函数是实现范型编程思想的基础

一般而言,父类指针只能指向父类的成员

1
School *sl = new School;

但是c++中,父类指针也可以指向子类成员

1
School *sl = new Student;

不过不能访问子类的成员

虚函数

  • 前提条件:
    • 子类中存在和父类中同名的函数
    • 指针对象
  • 特点:
    • 父类中为虚函数,子类默认也会为虚函数,但是省略了virtual。如果子类被继承,那么子类调用其子类也会是调用其子类的函数
    • 不能是inline函数
    • 构造函数不能是虚函数

虚函数可以让父类的指针不仅指向子类,还能使用子类的函数(为此,实现了多态)。只需要在父类的函数名前加virtual

其原理是:当对象以指针类型创建时,而且存在虚函数时,那么会创建一个虚表。虚表的首地址存在对象指针的前8字节,虚表数组中存储的是虚函数的地址。并且子类中复写的虚函数的地址会替换掉父类虚表中虚函数的地址

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
#include <iostream>
using namespace std;

class School
{
public:
int s_num = 10;
virtual void get_num() // 虚函数
{
cout << s_num << endl;
}
};

class Student : public School
{
public:
int s_num = 20;
void get_num()
{
cout << s_num << endl;
cout << School::s_num << endl;
}
};

int main(void)
{
School *sl = new Student; // 父类指针指向子类空间
sl->get_num(); //父类调用子类成员函数
delete sl;
return 0;
}

虚表

  • 对象创建时,如果存在虚函数,哪么会创建一个虚表来存放虚函数地址。对象的首地址(前八字节)存放的值为虚函数的首地址。取到前八字节的内容即取到了虚表的地址
  • 虚表中存放的都是虚函数的地址,由于每个函数的返回值不同,所以存储的内容大小不同,需要取前八字节来获得函数地址
  • 函数调用的实质是指针的调用,使用pointer()即可调用函数
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 School
{
public:
virtual void he()
{
cout << "hello" << endl;
}
int s_num = 10; // 不是虚函数不会存进虚表
virtual void get_num()
{
cout << "s_num" << endl;
}
};

class Student : public School
{
public:
virtual void he()
{
cout << "hello" << endl;
}
int s_num = 20;
void get_num()
{
cout << "s_num" << endl;
}
};

int main(void)
{
School *sh = new Student;
typedef void (*p)(); // 重定义函数指针类型为p
cout << sizeof(sh)<<endl;// 64位编译器对象指针为八字节
cout << *(long*)sh << endl; // 取到虚表的首地址
cout << ((long*)*(long*)sh + 0) << endl; //将虚表数组所有元素转换为8字节指针
((p)(*((long*)*(long*)sh+0)))();//取第一个元素(存放虚函数指针)的内容(函数指针),转换为函数指针类型来调用函数
((p)(*((long*)*(long*)sh+1)))();//调用第二个虚函数
int si = (int)(*((long*)*(long*)sh+1));//常量不会存在于虚表
cout << si << endl;
delete sh;
return 0;
}

8
94101996871008
0x5595d4189d60
hello
s_num
-736595012

虚析构

如果不让析构函数成为虚函数,那么父类指针指向子类时,释放父类指针时,只会调用父类的析构函数,不会调用子类的析构函数。所以需要在父类的析构函数成为虚析构,在析构函数前面加virtual

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
#include <iostream>
using namespace std;

class School
{
public:
virtual void he()
{
cout << "hello" << endl;
}
virtual ~School() // 虚析构
{
cout << "父类析构" << endl;
}
};

class Student : public School
{
public:
virtual void he()
{
cout << "hello" << endl;
}
virtual ~Student()
{
cout << "子类析构" << endl;
}
};

int main(void)
{
School *sh = new Student;
delete sh;
return 0;
}

子类析构
父类析构

纯虚函数

在父类中只声明了函数,没有函数主体的虚函数称为纯虚函数。

1
virtual void function_name() = 0;
  • 有纯虚函数的类,必须在子类中重写这个纯虚函数,才能实例化对象(子类)
  • 有纯虚函数的类,称为抽象类(不能实例化对象)
  • 全部为纯虚函数的类,称为接口类(可以包含成员变量,虚析构,构造函数)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

class School
{
public:
virtual void say() = 0; // 纯虚函数
};

class Student : public School
{
public:
virtual void say() // 重写纯虚函数
{
cout << "hello" << endl;
}
};

int main(void)
{
Student stu;
return 0;
}

虚继承

在多继承中存在一种情况:A为父类,B,C分别继承与A,D继承了B和C,那么D访问A中的成员时,就会出现访问不明确的问题。因为B,C继承时都是赋值了一份A中的成员,D访问时,不知道是访问B中的,还是C中的。所以需要虚继承来解决这个问题

1
2
3
4
5
// 在继承权限修饰符前加virtual
class A(){};
class B():virtual public A{};
class C():virtual public C{};
class D():public B, public C{};

虚继承存在的问题:结构复杂,内存消耗大。

单例模式

一个类只能有一个实例化对象,如果要实例化其他的对象,需要将之前的对象删除掉。为了实现这种设计模式,需要满足的条件:

  • 构造函数是private或者protected(这样就不能在类外直接实例化对象)
  • 类内部使用static函数实现对类的实例化(使用static函数返回一个对象指针)
  • 使用一个static变量作为标记进行判断(达到只实例化一次的目的)
  • 析构函数中改变static变量标记值(实现删除对象后可以再次实例化)
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
#include <iostream>
using namespace std;

class St
{
private:
St()
{
cout << "实例化对象" << endl;
}
public: //public修饰的静态函数
static int flag;
static St* init()
{
if(flag == 0)
{
flag = 1; //实例化一次之后进行标记
return new St;
}
else
{
return NULL; //第二次实例化的时候返回一个NULL空值
}
}
~St()
{
flag = 0; //析构时将flag的值改为0
}
};
int St::flag = 0; //类中的静态变量需要在外部进行初始化,因为在类中只有在实例化对象的时候才会进行数据的初始化,但是静态变量需要在程序开始时就进行初始化

int main(void)
{
St *st1 = St::init();
delete st1;
St *st2 = St::init();
delete st2;
return 0;
}

异常

1
2
3
4
5
6
7
8
9
try
{

}
throw a;
catch(a) //catch(...)表示其它所有情况
{

}

catch可以进行重载,以捕捉到各种异常。

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
#include <iostream>
using namespace std;

int main(void)
{
double a ;
double b ;
cin >> a >> b;
try
{
cout << a << " " << b << endl;
if(b == 0)
{
char e[] = "error !";
throw e;
}
else
{
double c = a / b;
cout << a << "/" << b << "= " << c << endl;
}
}
catch(char e[])
{
cout << e << " second number cannot be 0" << endl;
}
catch(...)
{
cout << "second number cannot be 0" << endl;
}
return 0;
}

内部类

即一个类中嵌套另外一个类,这种情况比较麻烦

  • 外部类访问内部类成员:需要在外部类中实例化一个内部类的对象。通过对象去访问
  • 内部类访问外部类成员:需要在外部类实例化外部类,并且将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
#include <iostream>
using namespace std;

class Ot
{
public:
int a = 10;
class Is
{
public:
Ot *outside; // 实例化外部类指针
int a;
Is(Ot *o):outside(o) //构造函数接收外部类传递进来的this指针
{
a = outside->a;
}
void show()
{
cout << outside->a << endl;
}
};
public:
Is inside; // 实例化内部类
Ot():inside(this){}; // 构造函数中将this指针传递给内部类
};

int main(void)
{
Ot outside;
outside.a = 30;
cout << "内部类 a= " << outside.inside.a << endl;
outside.inside.show();
return 0;
}

函数模板

通过函数模板可以实现之前函数重载的功能,对传入参数进行智能识别

1
2
template<typename customer_type_name>
return type fun_name(customer_type_name arg){}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
using namespace std;

template<typename T> // 函数模板
void num(T n) // 实现对n的数据类型的自动判断
{
cout << n+1 << endl;
}

int main(void)
{
num(3.3);
return 0;
}

函数模板的具体化

如果给函数传入一个结构体,而非普通的数据类型,那么可以对函数模板进行具体化,让结构体走不同的模板

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
#include <iostream>
using namespace std;

template<typename T>
void num(T n)
{
cout << n << endl;
}

struct list
{
int a;
int b;
};
template<> void num<list>(list li)
{
cout << li.a << " " << li.b << endl;
}

int main(void)
{
num("hello");
list lt = {10 , 20};
num(lt);
return 0;
}

类模板

如果类在实例化的时候构造函数需要传入参数,而这个参数类型不确定,那么可以使用类模板来达到自动识别参数类型的作用(实例化对象的时候手动指定类型)

1
2
3
4
5
6
7
8
9
template<typename customer_type_name>
class class_name
{
customer_type_name variable;
class_name(customer_type_name arg)
{
variable = arg;
}
}
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
#include <iostream>
using namespace std;

template<typename T = int> //可以设置默认类型
class Student
{
T num; //T类型
public:
Student(T n){ //接收T类型参数
num = n;
}
void get_n()
{
cout << num << endl;
}
};

int main(void)
{
Student<> stu1(22); //设置了默认类型不需要写int类型
stu1.get_n();
Student<char *> stu2((char *)"no.1"); //传入对应类型
stu2.get_n();
Student<double> *p = new Student<double>(12.12); //指针对象
p->get_n();
delete p;
return 0;
}

类外函数模板

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
#include <iostream>
using namespace std;

template<typename T = int>
class Student
{
T num;
public:
Student(T n){
num = n;
}
void get_n()
{
cout << num << endl;
}
void set_n(T n); //函数内定义
};

template<typename T> //函数外需要定义模板
void Student<T>::set_n(T n) //类需要指定类型T
{
num = n;
}

int main(void)
{
Student<> stu1(22);
stu1.get_n();
Student<char *> stu2((char *)"no.1");
stu2.get_n();
Student<double> *p = new Student<double>(12.12);
p->set_n(22.22); //调用类外函数赋值
p->get_n();
return 0;
}

22
no.1
22.22

继承类模板的使用

如果子类继承了一个有类模板的父类,那么在子类中需给定模板类型:可以给定指定的类型,也可以让子类拥有类模板,动态给定类型

指定固定类型

指定后父类的模板类型固定

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
#include <iostream>
using namespace std;

template<typename T = int>
class Student
{
T num;
public:
Student(T n){
num = n;
}
void get_n()
{
cout << num << endl;
}
void set_n(T n);
};

template<typename T>
void Student<T>::set_n(T n)
{
num = n;
}

class User : public Student<int> //子类指定父类固定类型
{
public:
User() : Student(100) //通过初始化列表给父类构造函数传参
{

}
};

int main(void)
{
User user1;
user1.set_n(111.111);
user1.get_n();
return 0;
}

动态指定类型

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
#include <iostream>
using namespace std;

template<typename T = int>
class Student
{
T num;
public:
Student(T n){
num = n;
}
void get_n()
{
cout << num << endl;
}
void set_n(T n);
};

template<typename T>
void Student<T>::set_n(T n)
{
num = n;
}

template<typename U> //动态函数模板
class User : public Student<U>
{
public:
User() : Student<U>(100) //需要写动态类型
{

}
};

int main(void)
{
User<double> user1; //初始化需要指定类型
user1.set_n(111.111);
user1.get_n();
return 0;
}

多态模板

类似上面动态指定类型

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
#include <iostream>
using namespace std;

template<typename T = int>
class Student
{
T num;
public:
Student(T n)
{
num = n;
}
virtual void get_n()
{
cout << num << endl;
}
};


template<typename U>
class User : public Student<U>
{
public:
User():Student<U>(100){};
virtual void get_n()
{
cout << "虚函数" << endl;
}
};

int main(void)
{
Student<double> *p = new User<double>;
p->get_n();
delete p;
return 0;
}


虚函数