前言

基础

1. 数组

1. 一维数组定义

image-20220922222835705

2. 二维数组定义

image-20220922222925915

2. 函数

函数的定义一般主要有5个步骤:

  1. 返回值类型
  2. 函数名
  3. 参数表列
  4. 函数体语句
  5. return表达式
1
2
3
4
int add(int num1,int num2){
int sum=num1+num2;
return sum;
}

值传递时形参改变不了实参

2.1 函数的声明

作用:告诉编译器函数名称及如何调用函数,函数的实际主体可以单独定义。

image-20220922232305383

2.2 函数的分文件编写

作用:让代码结构更加清晰

函数分文件编写一般有4个步骤:

  1. 创建后缀为.h的头文件
  2. 创建后缀名为.cpp的源文件
  3. 在头文件中写函数的声明
  4. 在源文件中写函数的定义

注意include后面加绝对路径

3. 指针

3.1 指针基本作用

指针的作用:可以通过指针间接访问内存

  • 内存编号是从0开始记录的,一般用十六进制数字表示
  • 可以利用指针变量保存地址

3.2 指针的定义和使用

定义指针:数据类型*指针变量名:

1
2
3
4
5
6
7
8
9
// 1.定义指针:数据类型*指针变量名:
int a=10;
int *p;
p=&a;
// 2.使用指针
// 指针前加*代表解引用,找到指针指向的内存中的数据
*p=1000;
cout << "a=" << a << endl;
cout << "*p=" << *p << endl;

3.3 const修饰指针

const修饰指针有三种情况:

  1. const修饰指针—常量指针(指针的指向可以改,指针指向的值不能改)

    1
    const int *p=&a;
  2. const修饰常量—指针常量(指针的指向不可以改,指针指向的值可以改)

    1
    int * const p=&a;
  3. const既修饰指针,又修饰常量(指针的指向和指针指向的值都不可以改)

1
const int * const p=&a;

3.4 指针和数组

利用指针访问数组中元素

1
2
3
4
5
6
// 利用指针来访问数组中的元素
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int *p=arr; // 数组名就是数组的首地址
cout << "利用指针来访问第一个元素" << *p << endl;
p++;
cout << "利用指针来访问第二个元素" << *p << endl;

3.5 指针和函数

作用:利用指针作函数参数,可以修改实参的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void swap(int *p1,int *p2){
int temp=*p1;
*p1=*p2;
*p2=temp;
}
int main(){
int a=10;
int b=20;
swap(&a,&b);
cout << "a=" << a << endl;
cout << "b=" << b << endl;
system("pause");
return 0;
}

4. 结构体

4.1 基本概念

结构体属于用户自定义的数据类型,允许用户存储不同的数据类型

4.2 结构体定义和使用

语法:struct 结构体名{结构体成员列表};

通过结构体创建变量的方式有三种:

  • struct 结构体名 变量名
  • struct 结构体吗 变量名={成员1值,成员2值…}
  • 定义结构体时顺便创建变量
1
2
3
4
5
6
7
8
struct Student{
string name;
int age;
int score;
}s3;// 顺便创建变量,一般用的很少
// 创建变量时可以省略struct
struct Student s1={"张三",21,100};
Student s2={"李四",21,100};

4.3 结构体指针

作用:通过指针访问结构体中的成员

  • 利用操作符->可以通过结构体指针访问结构体属性
1
2
3
4
5
6
7
8
struct Student{
string name;
int age;
int score;
};
struct Student s={"张三",18,100};
struct Student *p=&s;
cout << p->name << p->age << p->score;

核心

1. 内存分区模型

C++程序在执行时,将内存大方向划分为4个区域。

  • 代码区:存放函数的二进制代码,由操作系统进行管理
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束由操作系统回收

内存四区的意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程

1.1 程序运行前

在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域

代码区:

​ 存放CPU执行的机器指令

​ 代码区是共享的,共享的目的是对于频繁被执行的程序,只需 要在内存中有一份代码即可

​ 代码区是只读的,可防止程序意外修改了它的指令

全局区:

​ 全局变量和静态变量(static)存放在此

​ 全局区还包含了常量区、字符串常量和其他常量也存放在此

​ 该区域的数据在程序结束后由操作系统释放

1.2 程序运行后

栈区:

​ 由编译器自动分配释放,存放函数的参数值、局部变量等

​ 注意事项:不要返回局部变量的地址

堆区:

​ 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收

​ 在C++中主要利用new在堆区开辟内存

1
int *p = new int(10);

1.3 new操作符

C++利用new操作符在堆区开辟数据

​ 堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete

语法:new 数据类型

​ 利用new创建的数据,回返回该数据对应的类型的指针

2. 引用

2.1 引用的基本使用

作用:给变量起别名

语法:数据类型 &别名=原名

1
2
3
int a=10;
int &b=a;
cout << b <<endl; // 10

引用必须初始化.引用在初始化后,就不可以发生改变

2.2 引用做函数参数

作用:函数传参时,可以利用引用的技术让形参修饰实参

优点:可以简化指针修改实参

1
2
3
4
5
6
7
8
// 交换变量的值
void myswap(int &a, int &b){
int temp = a;
a = b;
b = temp;
}

myswap(a, b)

2.3 引用的本质

本质:引用的本质在C++内部实现是一个指针常量

3. 类和对象

3.1 封装

C++面向对象的三大特性为:封装、继承和多态

万事万物皆为对象,对象上有其属性和行为

封装的意义:

  • 将属性和行为作为一个整体,表现生活中的事物
  • 将属性和行为加以权限控制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Circle{
// 访问权限
public:
// 属性
int m_r;
// 行为
double cal(){
return 2*PI*m_r;
}
};

Circle c1;
c1.m_r=10;
cout << c1.cal() << endl;

类中的属性和行为统一称为成员

访问权限有三种:

  • public 公共权限
  • protected 保护权限
  • private 私有权限

struct和class的区别

struct和class的唯一区别在于默认的访问权限不同

  • struct默认权限为公共
  • class默认权限为私有

成员属性设置为私有:

优点

  • 将所有成员属性设置为私有,可以自己控制读写权限
  • 对于写权限,我们可以检测数据的有效性
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
class Person
{
public:
// 设置姓名:
void setName(string name){
m_Name=name;
}
// 获取姓名
string getName(){
return m_Name;
}
// 获取年龄
int getAge(){
m_Age=21; // 初始化年龄
return m_Age;
}
// 设置情人 只写
void setLover(string lover){
m_Lover=lover;
}
private:
// 姓名 可读可写
string m_Name;
// 年龄 只读
int m_Age;
// 情人 只写
string m_Lover;
};

Person p;
p.setName("张三");
cout << p.getName() <<endl;

3.2 对象的初始化和清理

如果我们不提供构造函数和析构函数,编译器会提供,编译器提供的构造函数和析构函数是空实现。

  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作

3.2.1 构造函数的分类和调用

两种分类方式:

​ 按参数分为:有参构造和无参构造

​ 按类型分为:普通构造和拷贝构造

三种调用方式:

​ 括号法

​ 显示法

​ 隐式转换法

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
// 构造函数分类
class Person{
public:
Person(){
cout << "Person无参数构造函数被调用" << endl;
}
Person(int a){
age=a;
cout << "Person有参数构造函数被调用" << endl;
}
// 拷贝构造函数
Person(const Person &p){
// 将传入的人身上的属性拷贝到我身上
age=p.age;
cout << "Person拷贝构造函数被调用" << endl;
}
int age;
};
void test01(){
// 1.括号法,调用默认构造函数的时候不要加()
Person p1;
Person p2(10);
Person p3(p2);
// 2.显示法
Person p4;
Person p5=Person(10);
Person p6=Person(p5);
// 3.隐式转换法
Person p7=10;
Person p8=p7;
}

3.3 C++对象模型和this指针

在C++中,类内的成员变量和成员函数分开存储

只有非静态成员变量才属于类的对象上

每个非静态成员函数只会诞生一份函数实例。也就是说多个同类型的对象会共用同一块代码

this指针指向被调用的成员函数所属的对象

this指针是隐含每一个非静态成员函数内的一种指针

this指针的用途:

  • ​ 当形参和成员变量同名时,可以用this指针来区分
  • ​ 在类的非静态成员函数中返回对象本身,可使用return *this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public:
Person(int age){
this->age=age;
}
Person& PersonAddAge(Person &p){
this->age+=p.age;
return *this;
}
int age;
};
void test01(){
Person p(18);
cout << "年龄为" << p.age <<endl;
}
void test02(){
Person p1(10);
Person p2(10);
// 链式编程思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout << "p2年龄为" << p2.age <<endl;
}

3.4 友元

在程序里有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术

友元的目的是让一个函数或者类访问另一个类中私有成员

友元的关键字为friend

友元的三种实现:

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

全局函数做友元

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Building{
// goodGay全局函数是Building好朋友,可以访问Building中私有成员
friend void goodGay(Building *builing);
public:
Building(){
m_sittingRoom="客厅";
m_bedroom="卧室";
}
public:
string m_sittingRoom; // 客厅
private:
string m_bedroom; // 卧室
};
// 全局函数
void goodGay(Building *builing){
cout << "好基友的全局函数正在访问:" << builing->m_sittingRoom <<endl;
cout << "好基友的全局函数正在访问:" << builing->m_bedroom <<endl;

}
void test01(){
Building building;
goodGay(&building);
}

类做友元

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
class Building{
friend class GoodGay;
public:
Building();
public:
string m_sittingroom;
private:
string m_bedroom;
};
class GoodGay{
public:
GoodGay();
public:
Building *building;
void visit(); // 参观函数,访问building中的属性
};
// 在类外写构造函数
Building::Building(){
m_sittingroom="客厅";
m_bedroom="卧室";
}
GoodGay::GoodGay(){
// 创建建筑物对象
building=new Building;
}
void GoodGay::visit(){
cout << "好基友类正在访问:"<< building->m_sittingroom <<endl;
cout << "好基友类正在访问:"<< building->m_bedroom <<endl;
}
void test01(){
GoodGay gg;
gg.visit();
}

成员函数做友元

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
class Building;
class GoodGay{
public:
GoodGay();
void visit(); // 让visit函数可以访问Building中私有成员变量
void visit2();
Building *building;
};
class Building{
friend void GoodGay::visit();
public:
Building();
public:
string m_sittingroom;
private:
string m_bedroom;
};
//类外实现
Building::Building(){
m_sittingroom="客厅";
m_bedroom="卧室";
}
GoodGay::GoodGay(){
building=new Building;
}
void GoodGay::visit(){
cout << "visit函数正在访问" << building->m_sittingroom <<endl;
cout << "visit函数正在访问" << building->m_bedroom<<endl;

};
void GoodGay::visit2(){
cout << "visit2函数正在访问" << building->m_sittingroom <<endl;
};
void test01(){
GoodGay gg;
gg.visit();
gg.visit2();
}

3.5 继承

继承是面向对象三大特征之一。

3.5.1 继承的基本语法

// 语法: class 子类 : 继承方式 父类
// 子类也称为派生类
// 父类也称为基类

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
// 继承实现
class BasePage{
public:
void header(){
cout << "公共头部" << endl;
}
void footer(){
cout << "公共底部" << endl;
}
};
class Java:public BasePage{
public:
void cont(){
cout << "Java" <<endl;
}
};
class Python:public BasePage{
public:
void cont(){
cout << "Python" <<endl;
}
};
void test01(){
Java java;
java.cont();
Python py;
py.cont();

}

3.5.2 继承方式

继承方式有三种:

  • 公共继承
  • 保护继承
  • 私有继承

image-20220928170133773

3.5.3 继承中的对象模型

父类中所有非静态成员属性都会被子类继承下去

父类中私有成员属性是被编译器给隐藏了,因此是访问不到的,但是确实被继承了

3.5.4 继承中的构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数

继承中的构造和析构的顺序如下:先构造父类再构造子类,析构顺序与构造顺序相反

3.5.5 继承同名成员处理方式

  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域

当子类与父类拥有同名的成员函数,子类会隐藏父类中所有同名成员函数,加作用域即可访问

4. 多态

4.1 多态的基本概念

多态分为两类:

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态的区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址