> 文章列表 > c++核心编程

c++核心编程

c++核心编程

c++核心编程

  • 序言
  • 1.内存分区模型
    • 1.1程序运行前
    • 1.2程序运行后
    • 1.3 new操作符
  • 2 引用
    • 2.1 引用的基本使用
    • 2.2 引用注意事项
  • 2.3 引用做函数参数
    • 2.4 引用做函数返回值
    • 2.5 引用的本质
    • 2.6 常量引用
  • 3.函数提高
  • 4.类与对象
    • 4.1封装
      • 4.1.1 封装的意义
      • 4.1.2 struct和class区别
    • 4.2 对象的初始化和清理
      • 4.2.1 构造函数和析构函数
      • 4.2.2 构造函数的分类及调用
      • 4.2.3 拷贝构造函数调用时机
      • 4.2.4 构造函数调用规则
      • 4.2.5 深拷贝与浅拷贝
      • 4.2.6 初始化列表
    • 4.4 友元
      • 4.4.1 全局函数做友元
    • 4.5 运算符重载
    • 4.6继承
      • 4.6.1 继承的基本语法
      • 4.6.2 继承方式
    • 4.7 多态
      • 4.7.1 多态的基本概念

序言

本文章主要介绍C++面向对象编程技术做了详细的讲解

1.内存分区模型

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

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

1.1程序运行前

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

代码区:

​ 存放 CPU 执行的机器指令

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

​ 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令

全局区:

​ 全局变量和静态变量存放在此.

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

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

  • C++中在程序运行前分为全局区和代码区
  • 代码区特点是共享和只读
  • 全局区中存放全局变量、静态变量、常量
  • 常量区中存放 const修饰的全局常量 和 字符串常量

1.2程序运行后

栈区:

由编译器自动分配释放, 存放函数的参数值,局部变量等
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

堆区:

​由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
在C++中主要利用new在堆区开辟内存

总结:

堆区数据由程序员管理开辟和释放

堆区数据利用new关键字进行开辟内存

1.3 new操作符

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

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

​ 语法: new 数据类型

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

示例1: 基本语法

int* func()
{int* a = new int(10);return a;
}int main() {int *p = func();cout << *p << endl;cout << *p << endl;//利用delete释放堆区数据delete p;//cout << *p << endl; //报错,释放的空间不可访问system("pause");return 0;
}

示例2:开辟数组

//堆区开辟数组
int main() {int* arr = new int[10];for (int i = 0; i < 10; i++){arr[i] = i + 100;}for (int i = 0; i < 10; i++){cout << arr[i] << endl;}//释放数组 delete 后加 []delete[] arr;system("pause");return 0;
}

2 引用

2.1 引用的基本使用

**作用:**给变量起别名

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

示例:

int main() {int a = 10;int &b = a;cout << "a = " << a << endl;cout << "b = " << b << endl;b = 100;cout << "a = " << a << endl;cout << "b = " << b << endl;system("pause");return 0;
}

2.2 引用注意事项

  • 引用必须初始化
  • 引用在初始化后,不可以改变

示例:

int main() {int a = 10;int b = 20;//int &c; //错误,引用必须初始化int &c = a; //一旦初始化后,就不可以更改c = b; //这是赋值操作,不是更改引用cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl;system("pause");return 0;
}

2.3 引用做函数参数

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

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

示例:

//1. 值传递
void mySwap01(int a, int b) {int temp = a;a = b;b = temp;
}//2. 地址传递
void mySwap02(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}//3. 引用传递
void mySwap03(int& a, int& b) {int temp = a;a = b;b = temp;
}int main() {int a = 10;int b = 20;mySwap01(a, b);cout << "a:" << a << " b:" << b << endl;mySwap02(&a, &b);cout << "a:" << a << " b:" << b << endl;mySwap03(a, b);cout << "a:" << a << " b:" << b << endl;system("pause");return 0;
}

总结:通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单

2.4 引用做函数返回值

作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用

用法:函数调用作为左值

示例

//返回局部变量引用
int& test01() {int a = 10; //局部变量return a;
}//返回静态变量引用
int& test02() {static int a = 20;return a;
}int main() {//不能返回局部变量的引用int& ref = test01();cout << "ref = " << ref << endl;cout << "ref = " << ref << endl;//如果函数做左值,那么必须返回引用int& ref2 = test02();cout << "ref2 = " << ref2 << endl;cout << "ref2 = " << ref2 << endl;test02() = 1000;cout << "ref2 = " << ref2 << endl;cout << "ref2 = " << ref2 << endl;system("pause");return 0;
}

2.5 引用的本质

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

2.6 常量引用

**作用:**常量引用主要用来修饰形参,防止误操作

在函数形参列表中,可以加const修饰形参,防止形参改变实参

3.函数提高

4.类与对象

C++面向对象的三大特性为:封装、继承、多态
C++认为万事万物都皆为对象,对象上有其属性和行为
例如:

​ 人可以作为对象,属性有姓名、年龄、身高、体重…,行为有走、跑、跳、吃饭、唱歌…

​ 车也可以作为对象,属性有轮胎、方向盘、车灯…,行为有载人、放音乐、放空调…

​ 具有相同性质的对象,我们可以抽象称为,人属于人类,车属于车类

4.1封装

4.1.1 封装的意义

封装是C++面向对象三大特性之一

封装的意义:

  • 将属性和行为作为一个整体,表现生活中的事物
  • 将属性和行为加以权限控制

封装意义一:

​ 在设计类的时候,属性和行为写在一起,表现事物

语法: class 类名{ 访问权限: 属性 / 行为 };

封装意义二:

类在设计时,可以把属性和行为放在不同的权限下,加以控制

访问权限有三种:

  1. public 公共权限 类内可以访问 类外可以访问
  2. protected 保护权限 类内可以访问 类外不可以访问
  3. private 私有权限 类内可以访问 类外不可以访问

4.1.2 struct和class区别

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

区别:

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

4.2 对象的初始化和清理

  • 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全
  • C++中的面向对象来源于生活,每个对象也都会有初始设置以及 对象销毁前的清理数据的设置。

4.2.1 构造函数和析构函数

对象的初始化和清理也是两个非常重要的安全问题

​ 一个对象或者变量没有初始状态,对其使用后果是未知

​ 同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题

c++利用了构造函数析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。

对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供

编译器提供的构造函数和析构函数是空实现。

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

构造函数语法:类名(){}

  1. 构造函数,没有返回值也不写void
  2. 函数名称与类名相同
  3. 构造函数可以有参数,因此可以发生重载
  4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

析构函数语法: ~类名(){}

  1. 析构函数,没有返回值也不写void
  2. 函数名称与类名相同,在名称前加上符号 ~
  3. 析构函数不可以有参数,因此不可以发生重载
  4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

示例

class Person
{
public://构造函数Person(){cout << "Person的构造函数调用" << endl;}//析构函数~Person(){cout << "Person的析构函数调用" << endl;}};void test01()
{Person p;
}int main() {test01();system("pause");return 0;
}

4.2.2 构造函数的分类及调用

两种分类方式:

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

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

三种调用方式:

​ 括号法

​ 显示法

​ 隐式转换法

示例

//1、构造函数分类
// 按照参数分类分为 有参和无参构造   无参又称为默认构造函数
// 按照类型分类分为 普通构造和拷贝构造class Person {
public://无参(默认)构造函数Person() {cout << "无参构造函数!" << endl;}//有参构造函数Person(int a) {age = a;cout << "有参构造函数!" << endl;}//拷贝构造函数Person(const Person& p) {age = p.age;cout << "拷贝构造函数!" << endl;}//析构函数~Person() {cout << "析构函数!" << endl;}
public:int age;
};//2、构造函数的调用
//调用无参构造函数
void test01() {Person p; //调用无参构造函数
}//调用有参的构造函数
void test02() {//2.1  括号法,常用Person p1(10);//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明//Person p2();//2.2 显式法Person p2 = Person(10); Person p3 = Person(p2);//Person(10)单独写就是匿名对象  当前行结束之后,马上析构//2.3 隐式转换法Person p4 = 10; // Person p4 = Person(10); Person p5 = p4; // Person p5 = Person(p4); //注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明//Person p5(p4);
}int main() {test01();//test02();system("pause");return 0;
}

4.2.3 拷贝构造函数调用时机

C++中拷贝构造函数调用时机通常有三种情况

  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数参数传值
  • 以值方式返回局部对象

4.2.4 构造函数调用规则

默认情况下,c++编译器至少给一个类添加3个函数

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

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

3.默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

  • 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造

  • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数

4.2.5 深拷贝与浅拷贝

深浅拷贝是面试经典问题,也是常见的一个坑

浅拷贝:简单的赋值拷贝操作

深拷贝:在堆区重新申请空间,进行拷贝操作

总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题

4.2.6 初始化列表

作用:

C++提供了初始化列表语法,用来初始化属性

语法:构造函数():属性1(值1),属性2(值2)... {}

4.4 友元

生活中你的家有客厅(Public),有你的卧室(Private)

客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去

但是呢,你也可以允许你的好闺蜜好基友进去。

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

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

友元的关键字为 friend

友元的三种实现

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

4.4.1 全局函数做友元

class Building
{//告诉编译器 goodGay全局函数 是 Building类的好朋友,可以访问类中的私有内容friend void goodGay(Building * building);public:Building(){this->m_SittingRoom = "客厅";this->m_BedRoom = "卧室";}public:string m_SittingRoom; //客厅private:string m_BedRoom; //卧室
};void goodGay(Building * building)
{cout << "好基友正在访问: " << building->m_SittingRoom << endl;cout << "好基友正在访问: " << building->m_BedRoom << endl;
}void test01()
{Building b;goodGay(&b);
}int main(){test01();system("pause");return 0;
}

4.5 运算符重载

运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

示例:

class Person {
public:Person() {};Person(int a, int b){this->m_A = a;this->m_B = b;}//成员函数实现 + 号运算符重载Person operator+(const Person& p) {Person temp;temp.m_A = this->m_A + p.m_A;temp.m_B = this->m_B + p.m_B;return temp;}public:int m_A;int m_B;
};//全局函数实现 + 号运算符重载
//Person operator+(const Person& p1, const Person& p2) {
//	Person temp(0, 0);
//	temp.m_A = p1.m_A + p2.m_A;
//	temp.m_B = p1.m_B + p2.m_B;
//	return temp;
//}//运算符重载 可以发生函数重载 
Person operator+(const Person& p2, int val)  
{Person temp;temp.m_A = p2.m_A + val;temp.m_B = p2.m_B + val;return temp;
}void test() {Person p1(10, 10);Person p2(20, 20);//成员函数方式Person p3 = p2 + p1;  //相当于 p2.operaor+(p1)cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;Person p4 = p3 + 10; //相当于 operator+(p3,10)cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;}int main() {test();system("pause");return 0;
}

4.6继承

4.6.1 继承的基本语法

例如我们看到很多网站中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同

接下来我们分别利用普通写法和继承的写法来实现网页中的内容,看一下继承存在的意义以及好处
普通实现:

//Java页面
class Java 
{
public:void header(){cout << "首页、公开课、登录、注册...(公共头部)" << endl;}void footer(){cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;}void left(){cout << "Java,Python,C++...(公共分类列表)" << endl;}void content(){cout << "JAVA学科视频" << endl;}
};
//Python页面
class Python
{
public:void header(){cout << "首页、公开课、登录、注册...(公共头部)" << endl;}void footer(){cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;}void left(){cout << "Java,Python,C++...(公共分类列表)" << endl;}void content(){cout << "Python学科视频" << endl;}
};
//C++页面
class CPP 
{
public:void header(){cout << "首页、公开课、登录、注册...(公共头部)" << endl;}void footer(){cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;}void left(){cout << "Java,Python,C++...(公共分类列表)" << endl;}void content(){cout << "C++学科视频" << endl;}
};void test01()
{//Java页面cout << "Java下载视频页面如下: " << endl;Java ja;ja.header();ja.footer();ja.left();ja.content();cout << "--------------------" << endl;//Python页面cout << "Python下载视频页面如下: " << endl;Python py;py.header();py.footer();py.left();py.content();cout << "--------------------" << endl;//C++页面cout << "C++下载视频页面如下: " << endl;CPP cp;cp.header();cp.footer();cp.left();cp.content();}int main() {test01();system("pause");return 0;
}

继承实现:

//公共页面
class BasePage
{
public:void header(){cout << "首页、公开课、登录、注册...(公共头部)" << endl;}void footer(){cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;}void left(){cout << "Java,Python,C++...(公共分类列表)" << endl;}};//Java页面
class Java : public BasePage
{
public:void content(){cout << "JAVA学科视频" << endl;}
};
//Python页面
class Python : public BasePage
{
public:void content(){cout << "Python学科视频" << endl;}
};
//C++页面
class CPP : public BasePage
{
public:void content(){cout << "C++学科视频" << endl;}
};void test01()
{//Java页面cout << "Java下载视频页面如下: " << endl;Java ja;ja.header();ja.footer();ja.left();ja.content();cout << "--------------------" << endl;//Python页面cout << "Python下载视频页面如下: " << endl;Python py;py.header();py.footer();py.left();py.content();cout << "--------------------" << endl;//C++页面cout << "C++下载视频页面如下: " << endl;CPP cp;cp.header();cp.footer();cp.left();cp.content();}int main() {test01();system("pause");return 0;
}

总结:

继承的好处:可以减少重复的代码

class A : public B;

A 类称为子类 或 派生类

B 类称为父类 或 基类

派生类中的成员,包含两大部分:

一类是从基类继承过来的,一类是自己增加的成员。

从基类继承过过来的表现其共性,而新增的成员体现了其个性。

4.6.2 继承方式

继承的语法:class 子类 : 继承方式 父类

继承方式一共有三种:

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

4.7 多态

4.7.1 多态的基本概念

多态是C++面向对象三大特性之一

多态分为两类

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

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

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

下面通过案例进行讲解多态

class Animal
{
public://Speak函数就是虚函数//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。virtual void speak(){cout << "动物在说话" << endl;}
};class Cat :public Animal
{
public:void speak(){cout << "小猫在说话" << endl;}
};class Dog :public Animal
{
public:void speak(){cout << "小狗在说话" << endl;}};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编void DoSpeak(Animal & animal)
{animal.speak();
}
//
//多态满足条件: 
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象void test01()
{Cat cat;DoSpeak(cat);Dog dog;DoSpeak(dog);
}int main() {test01();system("pause");return 0;
}

总结:

多态满足条件

  • 有继承关系
  • 子类重写父类中的虚函数

多态使用条件

  • 父类指针或引用指向子类对象

重写:函数返回值类型 函数名 参数列表 完全一致称为重写