目录:
内存的分区模型
函数引用
函数提高
类和对象
内存的分区模型
c++程序在执行时,将内存大方向划分为4个区域
代码区:存放函数体的二进制代码,由操作系统进行管理的
全局区:存放全局变量和静态变量以及常量
栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
内存四区的意义:
不同区域存放的数据,赋予不同生命周期,给我们更大的灵活编程。
程序运行前:(全局区)
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域:
![](/qrcode.jpg)
代码区:
- 存放CPU执行的机器指令:二进制01010。
- 特点:
- 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。
- 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令。
全局区:
- 全局变量和静态变量存放在此。
- 全局区还包含了常量区,字符串常量和其他常量也存放在此。
- 特点:
- 该区域的数据在程序结束后由操作系统释放。
其他常量:const修饰的全局变量。(全局常量)
全局区存放的数据:全局变量、静态变量、常量。
局部变量:在函数体内声明的变量。包括main函数内。
全局变量:在函数体外声明的变量。
const修饰与否:
- 常量:其值不可修改。
- 变量:其值可修改。
static修饰与否:
- 静态:
- 非静态:
#include<iostream>
using namespace std;
// 创建全局变量
int g_a = 10;
int g_b = 20;
// const修饰的全局常量。
const int c_g_a = 10;
const int c_g_b = 20;
int main()
{
// 全局区:存放:全局变量、静态变量、常量。
// 创建普通局部变量
int a = 10;
int b = 20;
cout << "局部变量a的地址位:" << (int)&a << endl;
cout << "局部变量b的地址位:" << (int)&b << endl;
cout << 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 = 20;
cout << "静态变量s_a的地址位:" << (int)&s_a << endl;
cout << "静态变量s_b的地址位:" << (int)&s_b << endl;
cout << endl;
// 常量:分为字符串常量和const修饰的变量。
//
// 字符串常量:双引号引起来的字符串,都可以被称为字符串常量。"Hello world."。
cout << "字符串常量的地址为:" << (int)&"hello world" << endl;
// const修饰变量:还可以分为:const修饰的全局常量,const修饰的局部变量(局部常量)。
cout << "全局常量 c_g_a的地址为:" << (int)&c_g_a << endl;
cout << "全局常量 c_g_b的地址为:" << (int)&c_g_b << endl;
cout << endl; // 局部常量不在全局区。
const int c_l_a = 10;
const int c_l_b = 20;
cout << "局部常量 c_l_a的地址为:" << (int)&c_l_a << endl;
cout << "局部常量 c_l_b的地址为:" << (int)&c_l_b << endl;
system("pause");
return 0;
}
程序运行后:(栈区)
由编译器自动分配和释放,存放函数的参数值,局部变量等。
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放。
#include<iostream>
using namespace std;
int* func()
{
int a = 10; // 局部变量 存放在栈区,栈区的数据在函数执行完后自动释放。
return &a; // 返回局部变量的地址。
}
int main()
{
int* p = func();
cout << *p << endl; // 10
cout << *p << endl; // 2055768456
system("pause");
return 0;
}
堆区:new 和 delete
由程序员分配释放,若程序员不释放,程序结束时由操作系统回收。
在C++中主要利用new关键字在堆区开辟内存。
#include<iostream>
using namespace std;
int* func3()
{
//int a = 10;
//return &a;
//
// 利用 new关键字 将数据开辟到 堆区。
// 指针本质也是局部变量,也是放在栈区,只是指针保存的实际数据在堆区。
int* p = new int(10);
return p;
}
int main()
{
int* p = func3();
cout << *p << endl; // 10
cout << *p << endl; // 10
cout << *p << endl; // 10
system("pause");
return 0;
}
指针是一种类型。4个字节,保存在栈中,栈的数据内容为指向的内存地址。
用new关键字可以在内存堆中开辟空间存储数据。
A* p = new A(值);返回的是一个地址。
新建一个指向A类型的数据在堆中的地址这样一个变量。
可以由程序员释放内存空间:delete
#include<iostream>
using namespace std;
int* func03()
{
// 在堆区创建一个整型数据。
// new 返回的是:该数据类型的指针。
int* p = new int(10);
return p;
}
void test01()
{
int* p = func03();
cout << *p << endl; // 10
cout << *p << endl; // 10
// 在堆中的数据,只要程序员不主动释放,那么就一直存在。
// 堆区的数据,由程序员管理开辟,程序员管理释放。
// 如果想释放堆区的数据,利用关键字delete
delete p;
//cout << *p << endl; // 引发了异常:读取访问权限冲突。内存已经被释放,再次访问就是非法,会报错。
}
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;
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
引用
引用的基本使用
作用:给变量起别名
语法:数据类型 &别名 = 原名
例如:
Int a = 10;
Int &b = a;
#include<iostream>
using namespace std;
int main()
{
//引用基本语法
//数据类型 &别名=原名
int a = 10;
int& b = a;
cout << "a=" << a << "b=" << b << endl;
b = 20;
//别名数据变 原也变
cout << "a=" << a << "b=" << b << endl;
system("pause");
return 0;
}
引用注意事项
- 必须初始化
- 引用在初始化后,不可以改变。
#include<iostream>
using namespace std;
int main()
{
//引用必须初始化
int a = 10;
int& b = a;
//引用初始化后,不可以改变
int c = 20;
b = c;//赋值操作
cout << "a=" << a << endl;
cout << "b=" << b << endl;
cout << "c=" << c << endl;
system("pause");
return 0;
}
引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参,
优点:可以简化指针修改实参
#include<iostream>
using namespace std;
//交换函数
//1、值传递
void myzhi(int a, int b)
{
int temp1 = a;
a = b;
b = temp1;
cout << "形参a=" << a << " b=" << b << endl;
}
//2、地址传递
void mydizhi(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
//3、引用传递
void yinyong(int& a, int& b)
{
int temp1 = a;
a = b;
b = temp1;
cout << "引用a=" << a << " b=" << b << endl;
}
int main()
{
int a = 15;
int b = 20;
int c = 22;
int d = 33;
myzhi(a, b);//值传递 形参不会修饰实参
cout << "值传递a=" << a << " b=" << b << endl;
mydizhi(&c, &d);
cout << "地址传递c=" << c << " d=" << d << endl;
yinyong(a, b);//相当于起了一个相同的别名进行传递
cout << "引用传递a=" << a << " b=" << b << endl;
system("pause");
return 0;
}
总结:引用传递和指针传递所得到的结果是相同的,引用传递是因为别名的原因。
引用做函数返回值
作用:引用时可以作为函数的返回值存在的,
注意:不要返回局部变量引用
用法:函数调用作为左值
#include<iostream>
using namespace std;
int& test1()
{
int a = 10;
return a;
}
int& test2()
{
static int a = 10;
return a;
}
int main()
{
int& b = test1();
cout << "b=" << b << endl;
cout << "b=" << b << endl;//乱码 引用已被释放
// 第一次结果正确,因为编译器做了保留
// 第二次结果错误,因为a的内存已经释放
//引用做函数的返回值
//1、不要返回局部变量的引用
int& c = test2();
cout << "全局c=" << c << endl;
cout << "全局c=" << c << endl;//全局 程序结束后才被释放
//2、函数的调用可以作为左值
test2() = 1000;//函数值调用在等号的左边存在:函数调用作为左值
cout << "左值c=" << c << endl;
cout << "左值c=" << c << endl;//全局 程序结束后才被释放
system("pause");
return 0;
}
引用的本质
本质:引用的本质在c++内部实现是一个指针常量
#include<iostream>
using namespace std;
//本质:引用的本质在C++内部实现是一个指针常量
void func(int& ref)
{
ref = 100;//ref是引用 转换为*ref=100
cout << "函数ref=" << ref << endl;
}
int main5()
{
int a = 10;
int& ref = a;//实际上是指针常量 int * const ref=a
//指针常量是指针指向的地址不可修改 而地址上的值是可以修改的。
ref = 20; //实际上是*ref=20
cout << "a=" << a << endl;
cout << "ref=" << ref << endl;
func(a);
system("pause");
return 0;
}
//C++推荐引用技术 因为语法上很简单 引用本质是指针变量 但是所有的指针操作编译器帮我们做
函数提高
函数默认参数
在c++中,函数的形参列表中的形参是可以有默认值的。
语法:返回值类型 函数名(参数=默认值){}
#include<iostream>
using namespace std;
int func(int a, int b = 10, int c = 10) {
return a + b + c;
}
//1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
//2. 如果函数声明有默认值,函数实现的时候就不能有默认参数
int func2(int a = 10, int b = 10);
int func2(int a, int b) {
return a + b;
}
int main() {
cout << "ret = " << func(20, 20) << endl;
cout << "ret = " << func(100) << endl;
system("pause");
return 0;
}
函数占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法: 返回值类型 函数名 (数据类型){}
在现阶段函数的占位参数存在意义不大,但是后面的课程中会用到该技术
#include<iostream>
using namespace std;
//函数占位参数 ,占位参数也可以有默认参数
void func(int a, int) {
cout << "this is func" << endl;
}
int main() {
func(10, 10); //占位参数必须填补
system("pause");
return 0;
}
函数重载概述
作用: 函数名可以相同,提高复用性
函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同 或者 个数不同 或者 顺序不同
注意: 函数的返回值不可以作为函数重载的条件
#include<iostream>
using namespace std;
//函数重载需要函数都在同一个作用域下
void func()
{
cout << "func 的调用!" << endl;
}
void func(int a)
{
cout << "func (int a) 的调用!" << endl;
}
void func(double a)
{
cout << "func (double a)的调用!" << endl;
}
void func(int a, double b)
{
cout << "func (int a ,double b) 的调用!" << endl;
}
void func(double a, int b)
{
cout << "func (double a ,int b)的调用!" << endl;
}
//函数返回值不可以作为函数重载条件
//int func(double a, int b)
//{
// cout << "func (double a ,int b)的调用!" << endl;
//}
int main() {
func();
func(10);
func(3.14);
func(10, 3.14);
func(3.14, 10);
system("pause");
return 0;
}
函数重载注意事项
- 引用作为重载条件
- 函数重载碰到函数默认参数
-
#include<iostream> using namespace std; //函数重载注意事项 //1、引用作为重载条件 void func12(int& a) { cout << "func (int &a) 调用 " << endl; } void func12(const int& a) { cout << "func (const int &a) 调用 " << endl; } //2、函数重载碰到函数默认参数 void func2(int a, int b = 10) { cout << "func2(int a, int b = 10) 调用" << endl; } void func2(int a) { cout << "func2(int a) 调用" << endl; } int main() { int a = 10; func12(a); //调用无const func12(10);//调用有const //func2(10); //碰到默认参数产生歧义,需要避免 system("pause"); return 0; }
类和对象
C++面向对象的三大特性为:封装、继承、多态。
C++认为万事万物都皆为对象,对象上有其属性和行为
例如:
人可以作为对象,属性有姓名、年龄、身高、体重...行为有走、跑、跳、吃饭、唱歌...
车也可以作为对象,属性有轮胎、方向盘、车灯...行为有载人、放音乐...
具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类
封装的意义
封装是C++面向对象三大特性之一
封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
1.将属性和行为作为一个整体,表现生活中的事物
语法:class 类名{ 访问权限: 属性 / 行为 };
示例1:设计一个圆类,求圆的周长
#include <iostream>
using namespace std;
//圆周率
const double PI = 3.14;
//设计一个圆类,求圆的周长
//圆求周长公式:2*PI*半径
//class代表设计一个类,类后面紧跟着的就是类的名称
class Circle {
//访问权限
//公共权限
public:
//属性
int r;//半径
//行为
//获取圆的周长
double calculateZC() { //calculate:计算
return 2 * PI * r;
}
};
int main() {
//通过圆类 创建具体的圆(对象)
//实例化(通过一个类 创建一个对象的过程)
Circle c1;
//给圆对象 的属性进行赋值
c1.r = 10;
cout << "圆的周长为:" << c1.calculateZC() << endl;
return 0;
}
设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生好的姓名和学号
#include <iostream>
#include <string>
using namespace std;
class student{
public://公共权限
//类中的属性和行为 我们统一称为 成员
//属性 成员属性 成员变量
//行为 成员函数 成员方法
//属性
int id;
string name;
//行为
void show()
{
cout<<"姓名:"<<name<<" 学号:"<<id<<endl;
}
//给姓名赋值
void setname(string Name)
{
name=Name;
}
//给学号赋值
void setid(int ID)
{
id=ID;
}
};
int main() {
student s1;
s1.setid(1001);
s1.setname("张三") ;
s1.show();
student s2;
s2.id=1002;
s2.name="李四";
s2.show();
return 0;
}
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
- public 公共权限
- protected 保护权限
- private 私有权限
#include <iostream>
#include <string>
using namespace std;
//访问权限
//公共权限 public 成员 在类内可以访问 类外可以访问
//保护权限 protected 成员 在类内可以访问 类外不可以访问 (儿子也可以访问父亲保护内容)
//私有权限 private 成员 在类内可以访问 类外不可以访问 (儿子不可以访问父亲的私有内容)
class Person {
public:
//公共权限
string name;//姓名
void func() {
name = "张三";
car = "拖拉机";
password = 123456;
}
protected:
//保护权限
string car;//汽车
private:
//私有权限
int password;//银行卡密码
};
int main() {
//实例化具体对象
Person p1;
p1.name = "李四";
//p1.car="奔驰";
//p1.password=123;
p1.func();
return 0;
}
struct和class区别
在C++中struct和class唯一的区别就在于 默认的访问权限不同
区别:
- struct 默认权限为公共
- class 默认权限为私有
成员属性设置为私有
优点1: 将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
void setname(string Name)
{
name = Name;
}
string getname()
{
return name;
}
void setage(int Age)
{
if (age < 0 || age>150)//"||"或者
{
age = 18;
cout << "输入错误!" << endl;
return;
}
age = Age;
}
int getage()
{
return age;
}
void setlove(string Love)
{
love = Love;
}
private:
//私有权限
string name;//姓名 可读可写
int age;//年龄
string love;//爱人 只写
};
int main() {
//实例化具体对象
Person p;
p.setname("张三");
p.setlove("lili");
p.setage(28);
cout << "姓名为:" << p.getname() << endl;
cout << "年龄为:" << p.getage() << endl;
return 0;
}
立方体类
设计立方体类
设计立方体类(Cube)
求出立方体的面积和体积
分别用全局函数和成员函数判断两个立方体是否相等
#include <iostream>
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;
}
//获取高
int getH() {
return m_H;
}
//获取立方体的面积
int calculateS() {
return 2 * (m_L * m_W + m_L * m_H + m_W * m_H);
}
//获取立方体的体积
int calculateV() {
return m_L * m_W * m_H;
}
//利用成员函数判断两个立方体是否相等
bool isSameByClass(Cube& c) {
if (m_L == c.getL() && m_W == c.getW() && m_H == c.getH()) {
return true;
}
}
private:
int m_L; //长
int m_W; //宽
int m_H; //高
};
//利用全局函数判断 两个立方体是否相等
bool isSame(Cube& c1, Cube& c2) {
if (c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH()) {
return true;
}
}
int main17() {
//创建一个立方体对象
Cube c1;
c1.setL(10);
c1.setW(10);
c1.setH(10);
cout << "c1的面积为:" << c1.calculateS() << endl;
cout << "c1的体积为:" << c1.calculateV() << endl;
//创建第二个立方体
Cube c2;
c2.setL(10);
c2.setW(10);
c2.setH(10);
//利用全局函数判断的
bool ret = isSame(c1, c2);
if (ret) {
cout << "全局函数判断:c1和c2是相等的" << endl;
}
else {
cout << "全局函数判断:c1和c2是不相等的" << endl;
}
//利用成员函数判断
ret = c1.isSameByClass(c2);
if (ret) {
cout << "成员函数判断:c1和c2是相等的" << endl;
}
else {
cout << "成员函数判断:c1和c2是不相等的" << endl;
}
system("pause");
return 0;
}
点和圆的关系
设计一个圆形类(Circle),和一个点类(Point),计算点和圆的关系
#include <iostream>
using namespace std;
//点和圆的关系案例
//点类
class Point {
public:
void setX(int x) {
m_X = x;
}
int getX() {
return m_X;
}
void setY(int y) {
m_Y = y;
}
int getY() {
return m_Y;
}
private:
int m_X;
int m_Y;
};
//圆类
class Circle {
public:
void setR(int r) {
m_R = r;
}
int getR() {
return m_R;
}
void setCenter(Point center) {
m_Center = center;
}
Point getCenter() {
return m_Center;
}
private:
int m_R; //半径
//在类中可以让另一个类 作为本类的一个成员
Point m_Center;
};
//判断点和圆的关系
void isInCircle(Circle& c, Point& p) {
//计算两点距离的平方
int distance =
(c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) + (c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY());
//计算半径的平方
int rDistance = c.getR() * c.getR();
//判断关系
if (distance == rDistance) {
cout << "点在圆上" << endl;
}
else if (distance > rDistance) {
cout << "点在圆外" << endl;
}
else {
cout << "点在圆内" << endl;
}
}
int main18() {
//创建圆
Circle c;
c.setR(10);
Point center;
center.setX(10);
center.setY(0);
c.setCenter(center);
//创建点
Point p;
p.setX(10);
p.setY(11);
isInCircle(c, p);
system("pause");
return 0;
}
对象的初始化和清理
- 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除一些自己信息数据保证安全
- C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置
构造函数和析构函数
对象和初始化和清理也是两个非常重要的安全问题
一个对象或者变量没有初始状态,对其使用后果是未知
同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器前置我们做的事情,因此如果我们不提供构造和析构,编译器会提供
编译器提供的构造函数和析构函数是空实现
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次
#include <iostream>
using namespace std;
//对象的初始化和清理
//1.构造函数 进行初始化操作
class person {
public:
person() {
cout<<"person 构造函数的调用"<<endl;
}
//2.析构函数 进行清理的操作
~person(){
cout<<"person 析构函数的调用"<<endl;
}
};
void test01() {
person p;//在栈上的数据,test01执行完毕后,释放这个对象
}
int main() {
test01();
person p;
return 0;
}
构造函数的分类及调用
两种分类方式:
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
三种调用方式:
括号法
显示法
隐式转换法
#include<iostream>
using namespace std;
//1.构造函数的分类和调用
//分类
// 按照参数分类 无参构造(默认构造)和有参构造
// 按照类型分类 普通构造和拷贝构造
class Person20
{
public:
//构造函数
Person20()
{
cout << "Person的无参构造函数的调用" << endl;
}
Person20(int a)
{
age = a;
cout << "Person的有参构造函数的调用" << endl;
}
//拷贝构造函数
Person20(const Person20& p)
{
//将传入的人身上的所有属性,拷贝到我身上
age = p.age;
cout << "Person的拷贝构造函数的调用" << endl;
}
//析构函数
~Person20()
{
cout << "Person的析构函数调用" << endl;
}
int age;
};
//调用
void test()
{
//1.括号法
Person20 p; //默认构造函数调用,p后面不加(),编译器会把Person p()看成一个函数的声明
Person20 p1(10); //调用有参构造函数
Person20 p2(p1); //调用拷贝构造函数
cout << "p1的年龄为:" << p1.age << endl;
cout << "p2的年龄为:" << p2.age << endl;
//2.显示法
Person20 p3 = Person20(10);//调用有参构造,Person(10)为匿名对象,p3为名字
Person20 p4 = Person20(p3);//调用拷贝构造
//Person(10);匿名对象,特点:当前执行结束后,系统会立即回收掉匿名对象
//Person(p4); 不要利用拷贝构造函数初始化匿名对象,编译器会认为Person(p4) == Person p4;
//3.隐式转换法
Person20 p5 = 10;//相当于写了Person p5 = Person(10); 有参构造
Person20 p6 = p5;//拷贝构造
}
int main()
{
test();
system("pause");
return 0;
}
拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
使用一个已经创建完毕的对象来初始化一个新对象
值传递的方式给函数参数传值
以值方式返回局部对象
#include <iostream>
using namespace std;
class Person21
{
public:
Person21()
{
cout << "Person 无参构造函数调用" << endl;
}
Person21(int a)
{
age = a;
cout << "Person 有参构造函数调用" << endl;
}
Person21(const Person21& p)
{
age = p.age;
cout << "Person 拷贝构造函数调用" << endl;
}
~Person21()
{
cout << "Person 析构函数调用" << endl;
}
int age;
};
// 1.使用一个已经创建完毕的对象来初始化一个新对象
void test021()
{
Person21 p1(20);
Person21 p2(p1);
cout << "p2的年龄: " << p2.age << endl;
}
// 2.值传递的方式给函数参数传值
void deal(Person21 p)
{
}
void test022()
{
Person21 p;
deal(p);
}
// 3.以值方式返回局部对象
Person21 deal2()
{
Person21 p;
cout << (int*)&p << endl;
return p;
}
void test23()
{
Person21 p = deal2();
cout << (int*)&p << endl;
}
int main() {
//test021();
//test022();
test23();
system("pause");
return 0;
}
构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
如果用户定义有参构造函数,c++不在提供无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数,c++不会再提供其他构造函数
#include <iostream>
using namespace std;
class Person22
{
public:
Person22()
{
cout << "person的无参函数调用" << endl;
}
Person22(int age)
{
m_Age = age;
cout << "person的有参函数调用" << endl;
}
/*Person22(const Person&p)
{
m_Age = p.m_Age;
cout << "Person的拷贝函数调用" << endl;
}*/
~Person22()
{
cout << "Person的析构函数调用" << endl;
}
int m_Age;
};
//void test01()
//{
// person p;
// p.m_Age = 18;
// person p2(p);//当我们自己没写拷贝构造函数是,编译器会自己写一个拷贝构造函数,m_age = p.m_age;
// cout << "p2的年龄是: " << p2.m_Age << endl;
//
//}
//如果写了有参构造函数,编译器就不会再提供无参构造函数,但依然会提供拷贝构造函数
void test22()
{
//Person p;
Person22 p(28);
Person22 p1(p);
cout << "p1的年龄是: " << p1.m_Age << endl;
}
//如果写了拷贝构造函数,编译器既不会提供无参构造函数也不会提供有参构造函数
//void test03()
//{
// Person p5;
//}
int main()
{
test22();
system("pause");
return 0;
}
深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作。
深拷贝:在堆区中重新申请空间,进行拷贝操作。
#include<iostream>
using namespace std;
//深拷贝与浅拷贝
class Person23
{
public:
Person23()
{
cout << "Person的默认构造函数调用" << endl;
}
Person23(int age, int height)
{
m_Age = age;
m_Height = new int(height);
cout << "Person的有参构造函数调用" << endl;
}
//自己实现拷贝构造函数 解决浅拷贝带来的问题
Person23(const Person23& p)
{
cout << "Person拷贝构造函数调用" << endl;
m_Age = p.m_Age;
//m_Height = p.m_Height;编译器默认实现就是这行代码
//深拷贝操作
m_Height = new int(*p.m_Height);
}
~Person23()
{
//析构代码,将堆区开辟数据做释放操作
if (m_Height != NULL)
{
delete m_Height;
m_Height = NULL;//防止出现野指针的情况
}
cout << "Person的有参构造函数调用" << endl;
}
int m_Age;//年龄
int* m_Height;//身高
};
void test23()
{
Person23 p1(18, 160);
cout << "p1的年龄为:" << p1.m_Age << "p1的身高为:" << *p1.m_Height << endl;
Person23 p2(p1);
cout << "p2的年龄为:" << p2.m_Age << "p2的身高为:" << *p2.m_Height << endl;
}
int main() {
test23();
system("pause");
return 0;
}
初始化列表
作用:
作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数( ):属性1(值1),属性2(值2)… { }
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
//初始化列表:初始化属性
Person() :m_A(10), m_B(20), m_C(30)
{
}
int m_A;
int m_B;
int m_C;
};
void test24()
{
Person p;
cout << "m_a=" << p.m_A << endl;
cout << "m_b=" << p.m_B << endl;
cout << "m_c=" << p.m_C << endl;
}
int main()
{
test24();
system("pause");
return 0;
}
类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
#include<iostream>
using namespace std;
//类对象作为类成员
//手机类
class Phone
{
public:
Phone(string pName)
{
cout << "Phone的构造函数运用" << endl;
m_PName = pName;
}
string m_PName;
};
//人类
class Person
{
public:
//Phone m_Phone =pName(隐式转换法)
Person(string name, string pName) :m_Name(name), m_Phone(pName)
{
cout << "Person的构造函数调用" << endl;
}
//姓名
string m_Name;
//手机
Phone m_Phone;
};
void test25()
{
Person p("张三","苹果MAX");
cout << p.m_Name << "拿着:" << p.m_Phone.m_PName << endl;
}
int main()
{
test25();
system("pause");
}
静态成员
静态成员就是在成员函数前加上关键字static,称为静态成员
静态成员分为:
静态成员变量:
1、所有对象共享同一份数据;
2、在编译阶段分配内存;
3、类内声明,类外初始化。
静态成员函数:
1、所有对象共享同一个函数;
2、静态成员函数只能访问静态成员变量。
静态成员变量
#include<iostream>
using namespace std;
class person
{
public:
static int m_A;
};
int main()
{
person p;
cout << p.m_A << endl;
system("pause");
return 0;
}
报错,无法解析p.m_A,因为类外无法访问,要先在类外初始化才行,而且必须是在全局范围初始化。
要写成以下形式
#include<iostream>
using namespace std;
class person
{
public:
static int m_A;
};
int person::m_A = 100; //类外初始化
int main()
{
person p;
cout << p.m_A << endl;
system("pause");
return 0;
}
在全局范围类外初始化要这样写:数据类型 类名::属性=初始化值
所有对象共享同一份数据
#include<iostream>
using namespace std;
class person
{
public:
static int m_A;
};
int person::m_A = 100;
int main()
{
person p;
cout << p.m_A << endl;
person p2;
cout << p2.m_A << endl;
system("pause");
return 0;
}
p和p2共享同一个m_A,所以都是100
扩展一个知识,可以用对象进行访问或者用类名进行访问
#include<iostream>
using namespace std;
class person
{
public:
static int m_A;
};
int person::m_A = 100;
int main()
{
person p;
cout << p.m_A << endl;
cout << person::m_A << endl;
system("pause");
return 0;
}
这里主函数里用两种方法访问m_A的值,都是正确的。因为这个m_A是所有对象公用的,所以可以通过类名访问,不需要写出是哪个对象的m_A
静态成员变量也是有访问权限的
#include<iostream>
using namespace std;
class person
{
private:
static int m_A;
};
int person::m_A = 100;
int main()
{
person p;
cout << p.m_A << endl;
cout << person::m_A << endl;
system("pause");
return 0;
}
把m_A的访问权限改为private,在主函数里就无法访问m_A了
静态成员函数
#include<iostream>
using namespace std;
class person
{
public:
static void func()
{
m_A = 100;
m_B = 200;//报错
cout << "func函数" << endl;
}
static int m_A;
int m_B;
private:
static void func2()
{
cout << "func2函数" << endl;
}
};
int person::m_A = 0;
int main()
{
person p;
p.func();//通过对象访问
person::func();//通过对象访问
p.func2();//报错
person::func2();//报错
system("pause");
return 0;
}
1、在类中m_B无法赋值,因为静态成员函数只能访问静态成员变量;
2、主函数里访问func2()函数报错,因为func2()函数时private的访问权限,类外无法访问。
成员变量和成员函数分类储存
只有非静态成员变量存储在类的对象中
注意:string 类型的数据 所占的内存为28 string是一个类
1.空对象有一个字节,用来区分对象
this指针概念
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这块代码是如何区分那个对象调用自己的呢?
c++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途:
当形参和成员变量同名时,可用this指针来区分
在类的非静态成员函数中返回对象本身,可使用return *this
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
//1、当形参和成员变量同名时,可用this指针来区分,解决名称冲突
//this->age 指向被调用对象成员变量
this->age = age;
}
Person& PersonAddPerson(Person& p) //引用返回
{
this->age += p.age;
//返回对象本身
return *this;
}
int age;
};
void test26()
{
//1.解决名称冲突
Person p1(10);
cout << "p1.age = " << p1.age << endl;
//2.返回对象本身
Person p2(10);
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1); //链式编程思想,自左向右执行
cout << "p2.age = " << p2.age << endl; //40
}
int main() {
test26();
system("pause");
return 0;
}
空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
#include<iostream>
using namespace std;
//空指针访问成员函数
class Person {
public:
void ShowClassName() { //空指针,可以调用成员函数
cout << "我是Person类!" << endl;
}
void ShowPerson() {
if (this == NULL) { //若空指针访问成员函数,返回
return;
}
cout << mAge << endl; //若空指针不能访问成员变量
}
public:
int mAge;
};
void test01()
{
Person* p = NULL;
p->ShowClassName(); //空指针,可以调用成员函数
p->ShowPerson(); //不能访问成员变量,成员函数中用this指针判断空指针,就可以了。
}
int main() {
test01();
system("pause");
return 0;
}
友元
生活中你的家有客厅(Public),有你的卧室(Private)
客厅所有来的客人都可以进去,但是你的卧室是私有的,
在程序里,有些私有属性 也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的就是让一个函数或者类 访问另一个类中私有成员
友元的关键字为 friend
友元的三种实现:
全局函数做友元
类做友元
#include<iostream>
#include<algorithm>
using namespace std;
class person
{
friend void friend_fun(person& p1);
public:
person()
{
m_money = 100;
}
private:
int m_money;
};
void friend_fun(person& p1)
{
cout << "money" << p1.m_money;
p1.m_money = 200;
cout << "money" << p1.m_money;
}
int main()
{
person p2;
friend_fun(p2);
}
运算符重载
运算符重载概念:对已有的运算符重新定义,赋予另一种功能,以适应不同的数据类型
加号运算符重载
作用:实现两个自定义数据类型相加的运算。
#include<iostream>
#include<algorithm>
using namespace std;
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 test29() {
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() {
test29();
system("pause");
return 0;
}