c++核心编程
本阶段主要针对c++面向对象编程技术做详细讲解,探讨c++中的核心和精髓
一、内存分区模型
c++程序在执行前,将内存大方向划分为4个区域
- 代码区:存放函数体的二进制代码,由操作系统进行管理
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
内存四区意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程
1 程序执行前
在程序编译后,生成EXE可执行程序,未执行该程序之前分为两个区域
代码区:
存放cpu执行的机器指令
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
全局区:
全局变量、静态变量和常量存放在此
全局区还包含了常量区,字符串区和其他变量也存放在此
该区域的数据在程序结束后由操作系统释放
全局变量:不在main函数及其他函数中的变量
#include<iostream>
#include<string>
using namespace std;
//全局变量
int g_a = 10;
int g_b = 10;
const int c_g_a = 10;
const int c_g_b = 10;
int main()
{
int a = 10;
int b = 10;
cout << "局部变量a的地址为:" << (int)&a << endl;
cout << "局部变量b的地址为:" << (int)&b << endl;
cout << "全局变量g_a的地址为:" << (int)&g_a << endl;
cout << "全局变量g_b的地址为:" << (int)&g_b << endl;
//静态变量,在普通变量前加static,属于静态变量
static int s_a = 10;
static int s_b = 10;
cout << "静态变量s_a的地址为:" << (int)&s_a << endl;
cout << "静态变量s_b的地址为:" << (int)&s_b << endl;
cout << "字符串常量的地址为:" << (int)&"helloworld" << endl;
cout << "常量之中const修饰的全局常量c_g_a的地址为:" << (int)&c_g_a << endl;
cout << "常量之中const修饰的全局常量c_g_b的地址为:" << (int)&c_g_b << endl;
const int c_l_a = 10;
const int c_l_b = 10;
cout << "常量之中const修饰的局部变量c_l_a的地址为:" << (int)&c_l_a << endl;
cout << "常量之中const修饰的局部变量c_l_b的地址为:" << (int)&c_l_b << endl;
system("pause");
return 0;
}
2.程序运行后
栈区
由编译器自动分配释放,存放函数的参数值,局部变量等
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
#include<iostream>
using namespace std;
int* func()
{
int a = 10; //这个局部变量存放在栈区,栈区的数据在函数执行完之后自动释放
return &a;
}
int main()
{
int* p = func();
cout << *p << endl; //第一次可以打印正确的数字,是因为编译器做了保留
cout << *p << endl; //第二次这个数据就不再保留
system("pause");
return 0;
}
但是在x64位的情况下,第二次的数据还会保留
堆区
由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
在c++中主要利用new在堆区开辟内存
#include<iostream>
using namespace std;
int* func()
{
int* p = new int(10); //new int(10)这里返回的是地址编号
return p;
}
int main()
{
int* p = func();
cout << *p << endl;
cout << *p << endl; //堆区的数据由程序员分配释放
system("pause"); //若程序员不释放,程序结束时由操作系统回收
return 0;
}
3. new操作符
c++利用new操作符在堆区开辟内存
堆区开辟的数据空间,由程序员手动开辟,手动释放,释放利用操作符delete
语法:new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针
#include<iostream>
using namespace std;
//1.new的基本语法
int* func()
{
int* p = new int(10); //在堆区创建一个整型数据,new返回的是该数据类型的指针
return p; //new一个什么类型,返回一个什么类型的指针
}
void test01()
{
int* p = func();
cout << *p << endl;
cout << *p << endl;
delete p; //程序员利用delete对堆区数据进行释放
cout << *p << endl; //释放之后再对其进行输出则会报错
}
int main()
{
test01();
system("pause");
return 0;
}
#include<iostream>
using namespace std;
//2.利用new创建堆区数组
void test02()
{
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[]arr; //堆区数组的释放语法
for (int i = 0; i < 10; i++) //释放之后再输出就会报错
{
cout << arr[i] << endl;
}
}
int main()
{
test02();
system("pause");
return 0;
}
二、引用
1. 引用的基本使用
作用:给变量起别名
语法:数据类型 &别名=原名
引用类型要与原名类型一致
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;
cout << "改变之前a=" << a << endl;
cout << "改变之前b=" << b << endl;
b = 1000;
cout << "改变之后a=" << a << endl;
cout << "改变之后b=" << b << endl;
}
/*输出结果为
改变之前a=10
改变之前b=10
改变之后a=1000
改变之后b=1000
*/
2.引用注意事项
- 引用必须初始化
- 引用在初始化后,不可以被改变
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;
//int& c; //会报错,引用必须初始化
int c = 20;
b = c; //这是赋值操作,不是更改引用
//&b = c; //编译器报错,这是更改引用,不能这样做——不可以更改引用
cout << a << endl;
cout << b << endl;
cout << c << endl;
system("pause");
return 0;
}
3.引用做函数参数
作用:函数做参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
#include<iostream>
using namespace std;
//1.值传递
void myswap01(int a, int b)
{
int temp = a;
a = b;
b = temp;
cout << "值传递中的a=" << a << endl;
cout << "值传递中的b=" << b << endl;
}
//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 << endl;
cout << "值传递交换后的b=" << b << endl;
myswap02(&a, &b);
cout << "地址传递交换后的a=" << a << endl;
cout << "地址传递交换后的b=" << b << endl;
myswap03(a, b);
cout << "引用传递交换后的a=" << a << endl;
cout << "引用传递交换后的b=" << b << endl;
system("pause");
return 0;
}
/*
值传递中的a=20
值传递中的b=10
值传递交换后的a=10
值传递交换后的b=20
地址传递交换后的a=20
地址传递交换后的b=10
引用传递交换后的a=10
引用传递交换后的b=20
*/
4.引用做函数返回值
作用:引用是可以作为函数的返回值而存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
#include<iostream>
using namespace std;
//1.不要返回局部变量的引用
int& test01() //如果不加那个&符的话,会报错
{
int a = 10;
return a;
}
int main()
{
int& b = test01();
cout << b << endl; //第一次可以成功输出,是因为编译器做了保留
cout << b << endl; //第二次结果错误,是因为局部变量在栈区,函数运行结束释放
system("pause");
return 0;
}
#include<iostream>
using namespace std;
//2.函数的调用可以作为左值
int& test02()
{
static int a = 10; //静态变量,存放在全局区
return a; //全局区上的数据在程序结束后系统释放
}
int main()
{
int& b = test02();
cout << b << endl;
cout << b << endl;
test02() = 1000; //如果函数的返回值是引用,这个函数调用可以作为左值
cout << b << endl;
system("pause");
return 0;
}
5.引用的本质
本质:引用的本质在c++内部实现是一个指针常量
#include<iostream>
using namespace std;
void func(int& ref)
{
ref = 100; //ref是引用,转换为*ref=100
}
int main()
{
int a = 10;
//自动转化为int * const ref=&a;指针常量是指针指向不可改,也说明为什么引用不可更改
int& ref = a;
ref = 20; //内部发现ref是引用,自动帮我们转换为*ref=20;
cout << "a:" << a << endl;
cout << "ref:" << ref << endl;
func(a);
system("pause");
return 0;
}
6.常量引用
使用场景:修饰形参,防止误操作
#include<iostream>
using namespace std;
int main()
{
int a = 10;
//int& ref = 10; //这句是错误的,引用必须引用一块合法的内存空间(栈区或堆区数据)
const int& ref = 10;
//编译器内部 int temp=10;const int &ref=temp;
//ref = 20; //这句也是错误的,加入const之后ref变为只读,不可修改
system("pause");
return 0;
}
#include<iostream>
using namespace std;
void showValue(int& value)
{
value = 1000;
cout << "value=" << value << endl;
}
void showValue1(const int& value)
{
//value = 1000; 此句报错,此函数中value的值不可以被修改
cout << "value=" << value << endl;
}
int main()
{
int a = 10;
showValue(a);
cout << "函数调用之后的a=" << a << endl;
system("pause");
return 0;
}
三、函数提高
1.函数默认参数
在c++中,函数的形参列表中的形参可以有默认值
语法:返回值类型 函数名 (参数=默认值) {}
#include<iostream>
using namespace std;
int func(int a, int b = 20, int c = 30)
{
return a + b + c;
}
int main()
{
cout << func(10) << endl;
cout << func(10, 30) << endl;
system("pause");
return 0;
}
注意:
- 如果自己传入,用自己传入的数据,如果没有,则用默认值
- 如果某个位置已经有了默认参数,那么从这个位置往后,从左往右必须有默认值
- 如果函数声明已经有默认参数,函数实现就不能有默认参数,否则会造成二义性,程序报错
#include<iostream>
using namespace std;
int func(int a = 10, int b = 20, int c = 30);
int func(int a = 10, int b = 20, int c = 30) //会报错,因为有二义性,编译器不知道是跟随声明还是实现
{
return a + b + c;
}
int main()
{
cout << func(10) << endl;
cout << func(10, 30) << endl;
system("pause");
return 0;
}
#include<iostream>
using namespace std;
int func(int a = 10, int b = 20, int c = 30);
int func(int a, int b, int c) //会有二义性,编译器不知道是跟随声明还是实现
{
return a + b + c;
}
int main()
{
cout << func(10) << endl;
cout << func(20, 60, 50) << endl;
system("pause");
return 0;
}
2.函数占位参数
c++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法:返回值类型 函数名 (数据类型){}
在现阶段函数的占位参数意义不大,但后面的课程会用到该技术
#include<iostream>
using namespace std;
//占位参数
//现阶段占位时,只能用到第一个10,用不到第二个占位的那个整型数据
void func(int a, int)
{
cout << "this is func" << endl;
}
void func1(int a, int = 30)
{
cout << "this is func1" << endl;
}
int main()
{
func(10, 5);
func1(10, 20);
system("pause");
return 0;
}
3.函数重载
3.1 函数名重载
作用:函数名可以相同,提高复用性
函数重载满足条件:
- 同一个作用域下
- 函数名相同
- 函数参数类型、个数、顺序不同
函数的返回值不可以作为函数重载的条件
#include<iostream>
using namespace std;
//函数重载
//1.在同一个作用域下,即都不在main函数都为全局作用域
void function()
{
cout << "function()的调用" << endl;
}
void function(int a)
{
cout << "function(int a)的调用" << endl;
}
void function(double a)
{
cout << "function(double a)的调用" << endl;
}
void function(int a, double b)
{
cout << "function(int a,double b)的调用" << endl;
}
void function(double a, int b)
{
cout << "function(double a,int b)的调用" << endl;
}
int main()
{
function();
function(10);
function(3.1415);
function(10, 3.1415);
function(3.1415, 20);
system("pause");
return 0;
}
3.2 函数重载注意事项
- 引用作为重载条件
- 函数重载碰到函数默认参数
当函数重载碰到默认参数,会出现二义性,程序会报错,尽量避免
#include<iostream>
using namespace std;
//1.引用作为重载条件
void fun(int& a)
{
cout << "fun(int &a)调用" << endl;
}
void fun(const int& a)
{
cout << "fun(const int& a)调用" << endl;
}
int main()
{
int a = 10;
fun(a); //调用第一个函数
fun(10); //调用第二个函数
system("pause");
return 0;
}
#include<iostream>
using namespace std;
//2.函数重载有默认参数
void fun(int a, int b = 10)
{
cout << "fun(int a,int b=10)调用" << endl;
}
void fun(int a)
{
cout << "fun(int a)调用" << endl;
}
int main()
{
int a = 10;
//fun(a); //会报错,因为两个函数都可以
fun(10, a);
system("pause");
return 0;
}
四、类和对象
c++中面向对象的三大特性为:封装、继承、多态
c++认为万事万物都皆为对象,对象有其属性和行为
具有相同性质的对象,可以抽象为类。
eg:
人可以作为对象,属性有姓名、年龄、身高、体重…,行为有走、跑、跳、吃饭、唱歌…
1.封装
1.1 封装的意义
封装是c++面向对象三大特性之一
封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
封装意义一:
在设计类的时候,属性和行为写在一起,表现事物
语法:class 类名 {访问权限 : 属性/行为};
#include<iostream>
using namespace std;
const double PI = 3.14;
//设计一个圆类,求圆的周长
class Circle
{
//访问权限
public:
//属性,一般使用变量
int m_r;
//行为,一般使用函数
double calculateZC()
{
return 2 * PI * m_r;
}
};
int main()
{
//通过圆类 创建具体的圆(对象)
//实例化——(通过一个类,创建一个对象的过程)
Circle c1;
c1.m_r = 10; //给属性赋值
cout << "圆的周长为:" << c1.calculateZC() << endl;
system("pause");
return 0;
}
示例:设计一个学生类,属性有姓名和学号,可以给学生的姓名和学号赋值,可以显示学生的姓名和学号
#include<iostream>
#include<string>
using namespace std;
//设计学生类
class Student
{
//访问权限
public: //公共权限
//属性,一般使用变量
string m_Name; //姓名
int m_Id; //学号
//行为,一般使用函数
void showStudent()
{
cout << "姓名为:" << m_Name << '\t';
cout << "学号为:" << m_Id << endl;
}
};
int main()
{
//通过学生类 创建具体的学生(对象)
Student s1;
s1.m_Name = "张三";
s1.m_Id = 1;
//显示学生信息
s1.showStudent();
Student s2;
s2.m_Name = "李四";
s2.m_Id = 2;
//显示学生信息
s2.showStudent();
system("pause");
return 0;
}
#include<iostream>
#include<string>
using namespace std;
//设计学生类
class Student
{
//访问权限
public: //公共权限
//类中的属性和行为,一般统称为成员
// 属性 也称为成员属性 成员变量
// 行为 也称为成员函数 成员方法
//属性,一般使用变量
string m_Name; //姓名
int m_Id; //学号
//行为,一般使用函数
void showStudent()
{
cout << "姓名为:" << m_Name << '\t';
cout << "学号为:" << m_Id << endl;
}
//2.利用行为给属性赋值
void setName(string name)
{
m_Name = name;
}
void setId(int id)
{
m_Id = id;
}
};
int main()
{
//通过学生类 创建具体的学生(对象)
Student s1;
s1.setName("张三");
s1.setId(1);
//显示学生信息
s1.showStudent();
system("pause");
return 0;
}
封装的意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
- public 公共权限
- protect 保护权限
- private 私有权限
#include<iostream>
using namespace std;
class Person
{
public:
string m_Name;
protected: //成员类内可以访问,类外不可以访问
string m_Car; //儿子可以访问父亲中的保护内容
private: //成员类内可以访问,类外不可以访问
int m_password; //儿子不可以访问父亲中的私有内容
public:
void func()
{
m_Name = "张三";
m_Car = "拖拉机";
m_password = 123456;
}
};
int main()
{
Person p1;
p1.m_Name = "李四";
//p1.m_Car = "奔驰"; //保护权限内容在类外访问不到
//p1.m_password = 123; //私有权限内容在类外访问不到
p1.func();
system("pause");
return 0;
}
1.2 struct和class区别
在c++中,struct和class唯一的区别就是在于默认访问权限不同
区别:
struct默认权限为公共——public
class默认权限为私有——private
#include<iostream>
using namespace std;
class C1
{
int m_a;
};
struct C2
{
int m_a;
};
int main()
{
C1 c1;
// c1.m_a = 100; //会报错
C2 c2;
c2.m_a = 1000; //不会报错
system("pause");
return 0;
}
1.3 成员属性设置为私有
优点1:将所有成员设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
//姓名的权限是可读可写
void setName(string name) //设置姓名
{
m_Name = name;
}
string getName() //获取姓名
{
return m_Name;
}
//年龄的权限也是可读可写,但是要设置0-150岁的范围
void setAge(int age)
{
if (age > 0 && age <= 150)
{
m_Age = age;
return;
}
m_Age = 0;
}
int getAge()
{
return m_Age;
}
//情人的权限是只写
void setLover(string lover)
{
m_Lover = lover;
}
private:
string m_Name;
int m_Age;
string m_Lover;
};
int main()
{
Person p1;
p1.setName("张三");
p1.setAge(1000);
p1.setLover("小王");
cout << "姓名为:" << p1.getName() << endl;
cout << "年龄为:" << p1.getAge() << endl;
system("pause");
return 0;
}
案例1:
设计一个立方体类
求出立方体的面积和体积
分别用全局函数和成员函数判断两个立方体是否相等
#include<iostream>
#include<string>
using namespace std;
/*设计步骤
1.创建立方体类
2.设计属性
3.设计行为——获取立方体面积和体积
4.分别利用全局函数和成员函数判断两个立方体是否相等
*/
class Cube
{
public:
//设置长
void setL(int l)
{
m_L = l;
}
//获取长
int getL()
{
return m_L;
}
//设置宽
void setW(int w)
{
m_W = w;
}
//获取宽
int getW()
{
return m_W;
}
//设置高
void setH(int h)
{
m_H = h;
}
//