c++面向对象编程
1.内存分区模型
程序运行前为代码区和全局区。程序运行后才有栈区和堆区。。
1.1 程序运行前
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
/*全局区全局变量、静态变量、常量 */
//全局变量
int g_1 = 20;
int g_2 = 30; //const修饰的全局变量,全局常量--也在全局区
const int c_g_a = 10;int main() {//普通局部变量--写到 main 函数体内, int a=10;int b=10;//静态变量static int s_1 = 40;//常量//字符串常量cout<<"字符串常量地址为:"<<(int)&"hello world"<<endl; //const修饰的常量const int c_a = 49; //不在全局区中,1.局部变量 2.const修饰的局部变量(局部常量)//在全局区中,1.静态变量 2. 静态变量 3.常量(字符串常量,const修饰的常量) return 0;
}
1.2 程序运行后
栈区:编译器自动分配和释放,存放函数参数值,局部变量等。
注意:不要返回局部变量地址,栈区开辟的数据由编译器自动释放
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
/* 栈区 --栈区数据由编译器管理开辟和释放 不要返回局部变量地址 */int *fu(){int a = 10; //局部数据,存放在栈区, return &a;
}
int main() {//接收返回值 int *p = fu();//第一次可以打印正确数据,因为编译器做了一次保留 cout<<*p<<endl;//第二次乱码 cout<<*p<<endl;return 0;
}
堆区:程序员分配释放,若程序员不释放,程序结束时由操作系统回收。
在C++中主要利用new在堆区开辟内存
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
/* 堆区 --堆区数据由程序员管理开辟和释放 */int *fu(){int *a = new int (10); //利用new 将数据开辟到堆区 return a;
}int main() {//接收返回值 int *p = fu();//输出10 cout<<*p<<endl;//输出10cout<<*p<<endl;return 0;
}
1.3 new操作符
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//1.new 基本用法
int *f(){//堆区创建整型数据//new 返回的是 该数据类型的指针int *p = new int(10);return p;
}
void test01(){int *p = f();cout<<*p<<endl;cout<<*p<<endl;//释放堆区数据 -使用delete delete p;//已释放,再次访问就是非法操作 //cout<<*p<<endl;
}
//堆区利用new开辟数组
void test02(){//创建10整型数据的数组,在堆区int *arr = new int[10];for(int i=0;i<10;i++){arr[i] = i+100;cout<<arr[i]<<endl;} //释放堆区数组--释放数组需要加 [] delete[] arr;
} int main() {return 0;
}
2.引用
2.1 引用的基本使用
作用:给变量起别名
语法:数据类型 &别名 = 原名
2.2 引用注意事项
1.引用必须初始化
2.引用初始化后不可改变
#include<iostream>
#include <bits/stdc++.h>
using namespace std;int main() {int a = 10;int &b = a;int c = 20;b = c; //赋值操作,不是更改引用 return 0;
}
2.3 引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//值传递
void swap1(int a,int b);
//地址传递
void swap2(int *a,int *b);{int t = *a;*a = *b;*b = t;
}
//引用传递
void swap3(int &a,int &b);{int t = a;a = b;b = t;
}
int main() {int a=1,b=3;swap1(a,b); //值传递swap2(&a,&b); //地址传递swap3(a,b); //引用传递 return 0;
}
2.4 引用做函数返回值
作用:引用可以作为函数返回值
注:不要返回局部变量引用
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//引用做函数返回值
int& test01(){int a = 1; //局部变量存放栈区 return a;
}
//函数调用可以作为左值
int& test02(){static int a = 10; //静态变量存放全局区,全局区数据程序结束后释放 return &a;
}
int main() {int &ref = test01();cout<<ref<<endl; //第一次打印正确,因为编译器做了保留 //cout<<ref<<endl; //再次打印乱码int &ref1 = test02();cout<<ref1<<endl; //多次打印输出都正确,输出1 cout<<ref1<<endl;//函数做左值,若函数返回值是引用,那么函数可作为左值test02() = 1000;cout<<ref1<<endl; //输出1000 cout<<ref1<<endl; return 0;
}
2.5 引用的本质
本质:引用的本质在C++内部实现是一个指针常量
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//发现是引用,转换为 int *const ref = &a;
void fc(int & ref){ref = 1000; //ref是引用,转换为 *ref = 1000;
}
int main() {int a = 10;//自动转换为 int *const ref = &a; 指针常量:指针指向不可改,也说明为什么引用不可更改int &ref = 0;ref = 20; // 内部发现ref是引用,自动转换为 *ref = 20;cout<<a<<endl;cout<<ref<<endl;fc(a); return 0;
}
2.6 常量引用
作用:修饰形参,防止误操作
函数形参中可以加const修饰形参,防止形参更改实参
#include<iostream>
#include <bits/stdc++.h>
using namespace std;void test01(const int &a){//不可修改 //a = 1000;cout<<a<<endl;
} int main() {/*int a = 20;int &ref = 10; //错误,引用必须引一块合法的内存空间//加上const之后,编译器将代码修改 int temp = 10; const int &ref = temp;const int& ref = 10; //正确,编译器优化代码,//加入const后变为只读,不可修改 //ref = 20;*/int a = 100;test01(a);system("pause");return 0;
}
3.函数提高
3.1 函数默认参数
c++中,函数的形参列表中的形参是可以有默认值的
语法:返回值类型 函数名 (参数=默认值) {}
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//函数默认参数
//若某个位置有了默认参数,那么后续的形参都要有默认参数,如b=10,则c必须要有值
int fc(int a,int b = 10,int c = 20){return a+b+c;
}
//声明和实现只能有一个有默认参数 ,否则会出现二义性问题
int fc2(int a=10,int b=10);
int fc2(int a,int b){return a+b;
}
int main() {//传入b=20,则使用20 cout<<fc(10,20)<<endl;//未传入b的值,使用10 cout<<fc(10)<<endl;system("pause");return 0;
}
3.2 函数占位参数
C++函数形参可以有占位参数,用来做占位,调用函数时必须填补该位置
语法:返回值类型 函数名(数据类型){},如 int f(int a,int);
int fc(int a,int);
int fc1(int a,int = 23);
3.3 函数重载
3.3.1 函数重载概述
作用:函数名可以相同,提高复用性
满足条件:
1.同一作用域
2.函数名称相同
3.函数参数类型不同 或者 个数不同 或者 顺序不同
注:函数返回值不可以作为函数重载的条件
//均是重载函数 函数重载需要函数都在同一个作用域下
void f1(int a);
void f1();
void f1(int a,int b);
void f1(int);
void f1(int a,double b);
void f1(double a,int b);
//非函数重载----函数返回值不可以作为函数重载的条件
int f2(int a);
double f2(int a);
3.3.2 函数重载注意事项
1.引用作为函数重载条件
2.函数重载碰到函数默认参数
#include<iostream>
#include <bits/stdc++.h>
using namespace std;//函数重载注意事项
//1.const可以作为函数重载条件
int f1(int &a);
int f1(const int &a);
//2.函数重载碰到默认参数
int f2(int a,int b=10);
int f2(int a);
int main() {//1.const可以作为函数重载条件 int a = 10;f1(a); //调用 int f1(int &a);f1(10); //调用 int f1(const int &a); 若调用 int f1(int &a); 相当于 int &A = 10 (不合法) //2.函数重载碰到默认参数f2(10); //既可以调用 int f2(int a,int b=10); 也可调用 int f2(int a); 出现二义性f2(1,10); //调用 int f2(int a,int b=10);return 0;
}
4 类和对象
C++面向对象三大特性:
封装 继承 多态
4.1 封装
4.1.1 封装的意义
封装是C++面向对象三大特性之一
封装的意义:
1.将属性和行为作为一个整体,表现生活中的事物
2.将属性和行为加以权限控制
封装的意义一:设计类时候,属性和行为写在一起,表现事物。
语法:class 类名 { 访问权限: 属性 / 行为 };
1.示例:设计圆类,求周长
#include<iostream>
#include <bits/stdc++.h>
using namespace std;const double PI = 3.14;
class Circle{//访问权限//公共权限:类内类外都可访问 //类中的属性和行为 都成为成员//属性:成员属性 成员变量//行为:成员函数 成员方法
public://属性//1.半径 int m_r; //行为 //1.获取圆周长double calZC(){return 2*PI*m_r;}//2.给半径赋值void setMR(int r){m_r = r;}
};
int main() {//1.通过圆类, 创建具体的圆(对象)//实例化,通过一个类创建一个对象的过程Circle c1;c1.m_r = 10; //或 c1.setMR(5);cout<<"周长:"<<c1.calZC()<<endl; return 0;
}
封装的意义二:类在设计时,可以把属性和行为放在不同的权限下,加以控制
三种权限:
1.public : 共有
2.protected:保护权限
3.private:私有权限
#include<iostream>
#include <bits/stdc++.h>
using namespace std;const double PI = 3.14;
class Circle{
//1.public : 公共权限 成员 类内可以访问 类外可以访问
public:string name;
//2.protected:保护权限 成员 类内可以访问 类外不可以访问 儿子可访问父亲保护内容
proteced:string car;
//3.private:私有权限 成员 类内可以访问 类外不可以访问 儿子不可访问父亲私有内容
private:string password;public:void func(){name = "123";car = "345";password = "2452432";}
};
int main() {Circle c1;c1.name = "王";//car 类外不可访问 c1.car = "米su7"; //password 类外不可访问 c1.password = "1234"; return 0;
}
4.1.2 struct和class区别
C++中struct和class唯一区别在于默认的访问权限不同
区别:struct默认权限公共,class默认权限为私有
4.1.3 成员属性设置为私有
优点1:将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,可以检测数据的有效性
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//成员属性设置为私有//1.可以自己控制读写权限//2.对于写可以检测数据有效性
class Circle{
public://通过公有方法对私有属性读写void setName(string name){m_name = name;} string getName(){return m_name;}void setAge(int age){if(age>0){m_age = age;}}int getAge(){return m_age;}void setIdo(int idol){m_idol = idol;}
private: string m_name; ///可读可写 int m_age = 18; //只读 也可以写 int m_idol; //只写
};int main() {Circle c1;c1.setName("张");cout<<c1.getName()<<endl;cout<<c1.getAge()<<endl;//读不到 cout<<c1.m_idol<<endl;return 0;
}
案例2 点和圆的关系
#include<iostream>
#include <bits/stdc++.h>
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,m_Y;
};
class Circle{
public:void setR(int r){m_R = r;}int getR(){return m_R;}void setCenter(Point p){m_Center = p;}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 rDis = c.getR() * c.getR();if(rDis == distance){cout<<"点在圆上"<<endl; }else if(rDis > distance){cout<<"点在圆外"<<endl; }else{cout<<"点在圆内"<<endl; }
}
int main() {//圆 Circle c1;//半径 c1.setR(10);//设置圆心 Point p; p.setX(10);p.setY(0);c1.setCenter(p);//创建点Point p1;p1.setX(10);p1.setY(10); isInCircle(c1,p1);return 0;
}
//正常实现需要分离为Point.h Point.cpp Circle.h Circle.cpp main.cpp
Point.h:
class Point{public:void setX(int x);int getX();void setY(int y);int getY();private:int m_X,m_Y;
};
Point.cpp:#include <Point.h>//需要指明所属作用域void Point::setX(int x){m_X = x;}int Point::getX(){return m_X;}void Point::setY(int y){m_Y = y;}int Point::getY(){return m_Y;}
Circle.h 和 Circle.cpp类似
4.2 对象的初始化和清理
4.2.1 构造函数和析构函数
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
class Circle{
public://1.构造函数,//无返回值;不用写void; 函数名与类名相同; 构造函数可有参数; 可重载;创建对象时候构造函数会自动调用,只调用一次Circle(){cout<<"构造"<<endl; } //2.析构函数//无返回值;不写void;函数名同类目 在名称前加~ ; 析构不可有参数;不可重载; 对象销毁前会自动调用析构函数,只调用一次~Circle(){cout<<"析构"<<endl; }
};
void test01(){Circle c;
}
int main() {//调用构造函数 和 析构 //因为test01中的Circle c是局部变量,是栈上的数据,执行完会释放对象 test01();//加上 system("pause"); 则系统会中断不停止,不会执行析构,不加还是会执行析构。点击按任意键继续后会执行析构 Circle c1;system("pause"); return 0;
}
4.2.2 构造函数的分类及调用
两种分类方式:
1.按参数分为:有参构造和无参构造
2.按类型分为:普通构造和拷贝构造
三种调用方式:
1.括号法
2.显示法
3.隐式转换法
#include<iostream>
#include <bits/stdc++.h>
using namespace std;class Circle{
public://普通构造 //无参和有参 Circle(){cout<<"构造"<<endl; } Circle(int a){cout<<"有参构造"<<endl; }//拷贝构造Circle(const Circle &c){cout<<"拷贝构造"<<endl; age = c.age;} ~Circle(){}int age;};
void test01(){//1.括号法Circle c; //默认构造调用Circle c1(10); //有参构造调用Circle c2(c1); //拷贝构造函数 //调用默认构造函数时候,不要加() ,此时执行则什么都不输出//因为下边代码,编译器会认为是一个函数的声明 Cirlce c3(); //2.显示法Circle c1;Cirlce c2 = Circle(10);Cirlce c3 = Circle(c2); //1.匿名对象, 特点:当前行执行结束后,系统会立即回收匿名对象。先执行构造再执行析构Circle(10); //2.不要利用拷贝构造函数初始化匿名对象。此时编译器会认为 Circle(c3) = Circle c3; 两个c3,名称重复 Circle(c3);//3.隐式转换法 //相当于 Cirlce c4 = Circle(10); Cirlce c4 = 10; //有参构造 Cirlce c5 = c4; //拷贝构造
}int main() {return 0;
}
4.2.3 拷贝构造函数的调用时机
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//拷贝构造调用时机
class Circle{
public:Circle(){cout<<"构造"<<endl; } Circle(int a){age = a;cout<<"有参构造"<<endl; }//拷贝构造Circle(const Circle &c){cout<<"拷贝构造"<<endl; age = c.age;} ~Circle(){}int age;};
//1.使用一个已经创建完毕的对象初始化一个新对象
void test01(){Circle c1(20);Circle c2(c1);
}
//2.值传递方式 给函数参数传值
void doWork(Circle c){}
void test02(){Circle c1;//执行函数过程也会执行 构造函数,因为是值传递 doWork(c1);
}
//3.值方式返回局部对象
Circle doWork2(){Circle c1;//返回过程会根据 c1 创建一个新的对象 return c1;
}
void test03(){//c和 doWork2中 Circle c1; 不是一个对象 Cirlce c = doWork2();
}
int main() {//调用两次构造 两次析构; Circle c1;一次。 doWork(c1);一次 testo3();//调用两次构造 两次析构:Circle c1;一次。return c1;一次 test03();return 0;
}
4.2.4 构造函数调用规则
提供有参构造,则编译器提供拷贝构造,但不提供无参构造
提供拷贝构造,则编译器有参构造和无参构造都不提供,需要手动编写
4.2.4 深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请框架,进行拷贝操作
浅拷贝
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//深拷贝和浅拷贝
class Circle{
public:Circle(){cout<<"构造"<<endl; } Circle(int a,int height){age = a;m_H = new int(height);cout<<"有参构造"<<endl; }~Circle(){//析构代码,将堆区开辟数据做释放操作if(m_H != NULL){delete m_H;//防止野指针 m_H = NULL;}cout<<"析构"<<endl; }int age;int *m_H;
};
void test01(){//先进后出,c2的析构先被释放 Circle c1(18,160);cout<<"c1年龄:"<<c1.age<<" c1身高:"<<*c1.m_H<<endl;Circle c2(c1);cout<<"c2年龄:"<<c2.age<<" c2身高:"<<*c2.m_H<<endl;
}int main() {test01();return 0;
}
浅拷贝的问题:堆区的内存重复释放。案例中p2先执行析构释放了 *m_H。然后p1又进行释放,重复释放引发异常。
解决:利用深拷贝解决。重新在堆区申请一块内存,使p1中的m_H和p2中的m_H申请不同的内存。
如果有堆区开辟的数据进行浅拷贝,则需要使用拷贝构造进行深拷贝。
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//深拷贝和浅拷贝
class Circle{
public:Circle(){cout<<"构造"<<endl; } Circle(int a,int height){age = a;m_H = new int(height);cout<<"有参构造"<<endl; }//自己实现拷贝构造函数,解决浅拷贝带来的问题Circle(const Cirlce &p){cout<<"拷贝构造函数"<<endl;age = p.age;//编译器默认执行的是 m_H = p.m_H;// 深拷贝操作m_H = new int(*p.m_H); } ~Circle(){//析构代码,将堆区开辟数据做释放操作if(m_H != NULL){delete m_H;//防止野指针 m_H = NULL;}cout<<"析构"<<endl; }int age;int *m_H;
};
void test01(){//先进后出,c2的析构先被释放 Circle c1(18,160);cout<<"c1年龄:"<<c1.age<<" c1身高:"<<*c1.m_H<<endl;Circle c2(c1);cout<<"c2年龄:"<<c2.age<<" c2身高:"<<*c2.m_H<<endl;
}
int main() {test01();return 0;
}
4.2.6 初始化列表
C++提供了初始化列表语法,用来初始化属性。
语法:构造函数():属性1(值1),属性2(值2),… {}
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//深拷贝和浅拷贝
class Circle{
public://传统方式 /*Circle(int a,int b,int c){a_1 = a;b_1 = b;c_1 = c;}*///初始化列表初始化属性Circle(int a,int b,int c) :a_1(a),b_1(b),c_1(c){}int a_1,b_1,c_1;
};
void test01(){//Circle c(1,2,3);Circle c(1,2,3); //等价 Circle c(1,2,3);
}
int main() {test01();return 0;
}
4.2.7 类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
class A{}
//B中有对象A作成员,A是对象成员,此时会先执行对象成员的构造函数,即先调用A的构造函数
class B{A a;
}
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//类对象作为类成员
class Phone{
public:Phone(string pName){m_Name = pName;cout<<"Phone构造函数"<<endl; }string m_Name;
};
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 test01(){Person p("张","苹果max");
}
int main() {test01();return 0;
}
4.2.8 静态成员
静态成员即在成员变量前加 static,称为静态成员。
静态成员变量
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//静态成员变量
class Person{
public://1.所有对象共享同一份数据//2.编译阶段就分配内存//3.类内声明,类外初始化操作 static int m_A;
};
void test01(){Person p;//出错,因为去找m_A具体值时候找不到,因此需要初始化 cout<<p.m_A<<endl;
}
int main() {test01();return 0;
}
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//静态成员变量
class Person{
public://1.所有对象共享同一份数据//2.编译阶段就分配内存//3.类内声明,类外初始化操作 static int m_A; //静态成员变量也有访问权限
private:static int m_B;
};
int Person::m_A = 100;
int Person::m_B = 100;void test01(){Person p;cout<<p.m_A<<endl;Person p1;p1.m_A = 200;//打印200 cout<<p.m_A<<endl;
}
void test02(){//静态成员变量,不属于某个对象上,所有对象共享同一份数据// 因此静态成员变量有两种访问方式//1.通过对象进行访问Person p; cout<<p.m_A<<endl;//2.通过类名进行访问cout<<Person::m_A<<endl;//私有作用域:无法通过这种方式访问 cout<<Person::m_B<<endl;
}
int main() {test01();return 0;
}
静态成员函数
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//静态成员函数
//所有对象共享同一个函数
//静态成员函数只能访问静态成员变量
class Person{
public:static void func(){m_A = 100;//静态成员函数,不可以访问,非静态成员变量,因为无法区分到底是哪个对象的 m_B m_B = 200;cout<<"static void func 调用"<<endl;}static int m_A; int m_B;//静态成员函数的访问权限
private:static void func2(){cout<<"static void func2 调用"<<endl;}};
int Person::m_A = 10;//两种访问方式
void test01(){//1.通过对象访问Person p;p.func();//2.通过类名访问Person::func();//私有作用域下无法访问 ,类外访问不到私有静态成员函数 Person::func2();
}
int main() {test01();return 0;
}
4.3 c++对象模型和this指针
4.3.1 成员变量和成员函数分开存储
C++中,类内的成员变量和成员函数分开存储。
只有非静态成员变量才属于类的对象上
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//成员变量和成员函数是分开存储的
class Person{
};
class Person1{int m_A;
};
class Person2{int m_A;static int m_B;
};
class Person3{void func();
};
void test01(){Person p;//空对象占用的内存空间为 1 //C++会给每个空对象也分配一个字节空间,为了区分空对象占内存的位置,每个空对象也应有内存地址 cout<<"sizeof(p):"<<sizeof(p)<<endl;
}
void test02(){Person1 p1;//输出 4 cout<<"sizeof(p1):"<<sizeof(p1)<<endl;
}
void test03(){Person2 p2;//输出 4, 静态成员变量在对象中不占内存空间 cout<<"sizeof(p2):"<<sizeof(p2)<<endl;
}
void test04(){Person3 p3;//输出1 cout<<"sizeof(p3):"<<sizeof(p3)<<endl;
}
int main() {test01(); test02(); test03(); test04();return 0;
}
4.3.2 this指针概念
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//this指针
class Person{
public:Person(int age){//不加this会出问题,this指针指向的是被调用的成员函数 所属的对象 this->age = age;}int age;Person& PersonAddAge(Person &p){this->age += p.age;//this指向p2的指针,*this指向p2本身 return *this;}
};
//1.解决名称冲突
void test01(){Person p(19);cout<<p.age<<endl;
}
//2.返回对象本身用*this
void test02(){Person p1(10);Person p2(19);//输出 19+10+10p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);cout<<p2.age<<endl;
}
int main() {test01();test02();return 0;
}
4.3.3 空指针访问成员函数
C++中空指针也是可以调用成员函数的, 但是也要注意有没有用到this指针。
若用到this指针,需要加以判断保证代码健壮性。
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//空指针调用成员函数
class Person{
public:void showClassName(){cout<<"this showClassName"<<endl;}void showAge(){// 等价于 cout<<"age= "<<this->m_age<<endl; cout<<"age= "<<m_age<<endl;}int m_age;
};
void test01(){Person *p = NULL;//正常调用 p->showClassName();//提示空指针 p->showAge();
}
int main() {test01();return 0;
}
4.3.4 const修饰成员函数
常函数和常对象
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//空指针调用成员函数
class Person{
public://this指针本质:是指针常量 指针的指向不可修改 指向的值可修改 void showAge1(){//可修改 m_age = 100; //this = NULL; //this指针不可修改指针指向 }//加上 const后 this指针的指向不可修改 指向的值也不可修改 void showAge() const {//m_age = 100; this->m_b = 23;}int m_age;mutable int m_b; //特殊变量 使其在常含数也可修改,加上关键字 mutable
};
//常函数
void test01(){Person p;p.showAge();
}
//常对象
void test02(){const Person p; //在对象前加const,变为常对象//p.m_age = 20; //不可修改p.m_b = 234; //可修改,添加了mutable的成员变量在常对象下也能修改 //常对象只能调用常含数,不能调用普通成员函数,因为普通成员函数可修改不加mutable的属性 p.showAge();
}
int main() {test01();test02();return 0;
}
4.4 友元
友元的三种实现:
1.全局函数友元
2.类做友元
3.成员函数做友元
4.4.1 全局函数做友元
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//
class Building{//goodGay全局函数是 Building好朋友,可以访问 Building 中私有成员 friend void goodGay(Building *build);
public:Building(){m_setRoom = "客厅";m_bedRoom = "卧室"; }
public:string m_setRoom;
private:string m_bedRoom;
};
//全局函数做友元:把函数放入类中即可
//访问build的全局和私有属性
void goodGay(Building *build){cout<<"goodGay正在访问:"<< build->m_setRoom<<endl;//出错,私有属性无法访问,此时将本函数全部放入building类中 即可访问 cout<<"goodGay正在访问:"<< build->m_bedRoom<<endl;
}
void test01(){Building build;goodGay(&build);
}
void test02(){}
int main() {test01();test02();return 0;
}
4.4.2 类做友元
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//类做友元
class Building;
class GoodGay{public:GoodGay();void visit(); //参观函数访问Building类中的属性Building * build;
};
class Building{//加入后 GoodGay 中的函数即可访问 Building 的私有成员 friend class GoodGay;public:Building();public:string m_setRoom;private:string m_bedRoom;
};
//类外写成员函数
Building::Building(){m_setRoom = "客厅";m_bedRoom = "卧室";
}
GoodGay::GoodGay(){build = new Building;
}
void GoodGay::visit(){cout<<"GoodGay正在访问:"<< build->m_setRoom<<endl;//此时不可访问,此时只需要将 friend class GoodGay; 加入Building中即可访问 cout<<"GoodGay正在访问:"<< build->m_bedRoom<<endl;
}
void test01(){GoodGay gg;gg.visit();
}int main() {test01();return 0;
}
4.3.4 成员函数做友元
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//成员函数做友元
class Building;
class GoodGay{public:GoodGay();void visit(); //让 visit 函数可访问Building类中的私有属性void visit1(); //不可访问 Building类中的私有属性Building * build;
};
class Building{//告诉编译器 GoodGay 中的visit函数作为友元函数,可访问 Building 的私有成员 friend void GoodGay::visit(); public:Building();public:string m_setRoom;private:string m_bedRoom;
};
//类外写成员函数
Building::Building(){m_setRoom = "客厅";m_bedRoom = "卧室";
}
GoodGay::GoodGay(){build = new Building;
}
void GoodGay::visit(){cout<<"visit 正在访问:"<< build->m_setRoom<<endl;//此时不可访问,此时只需要将 friend void GoodGay::visit(); 加入Building中即可访问 cout<<"visit 正在访问:"<< build->m_bedRoom<<endl;
}
void GoodGay::visit1(){cout<<"visit1 正在访问:"<< build->m_setRoom<<endl;//此时不可访问//cout<<"visit1 正在访问:"<< build->m_bedRoom<<endl;
}
void test01(){GoodGay gg;gg.visit();gg.visit1();
}
int main() {test01();return 0;
}
4.5 运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予另一种功能,以适应不同的数据类型。
4.5.1 加号运算符重载
作用:实现两个自定义数据类型相加的运算。
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//加号运算符重载
class Person
{
public://1.成员函数重载 + 号
/* Person operator+(Person &p){Person temp;temp.ma = this->ma + p.ma;temp.mb = this->mb + p.mb;} */
public:int ma;int mb;
};
//2.全局函数重载 + 号
Person operator+(Person &p1,Person &p2){Person temp;temp.ma = p1.ma + p1.ma;temp.mb = p2.mb + p2.mb;return temp;
}
//运算符重载也可以发生函数重载
Person operator+(Person &p1,int a){Person temp;temp.ma = p1.ma + a;temp.mb = p1.mb + a;return temp;
}
void test01(){Person p1;p1.ma = 1;p1.mb = 2;Person p2;p2.ma = 1;p2.mb = 2;Person p3 = p1 + p2;cout<<"p3.ma: "<<p3.ma<<" p3.mb: "<<p3.mb<<endl;Person p4 = p3 + 100;cout<<"p4.ma: "<<p4.ma<<" p4.mb: "<<p4.mb<<endl;
}
int main() {test01();return 0;
}
总结:1.对于内置的数据类型的表达式的运算符是不可能改变的
2.不要滥用运算符重载
4.5.2 左移运算符重载
作用:可以输出自定义数据类型
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//左移运算符重载
class Person
{friend ostream & operator<<(ostream &cout,Person &p);
public://利用成员函数重载,左移运算符. p.opeartor<<(cout) 简化版本 p << cout//不会利用成员函数重载 << 运算符,因为无法实现cout在<<左侧 //void opeartor<<(){ }Person(int a,int b){ma = a;mb = b;}
private:int ma;int mb;
};
//只能利用全局函数重载 左移运算符
ostream & operator<<(ostream &cout,Person &p){ //本质 opeartor<< (cout ,p) 简化 cout<< cout<<"ma:"<<p.ma<<" mb:"<<p.mb;return cout;
}
//未返回cout,不能追究endl
/*void operator<<(ostream &cout,Person &p){ //本质 opeartor<< (cout ,p) 简化 cout<< cout<<"ma:"<<p.ma<<" mb:"<<p.mb<<endl;
} */
// 输出对象p,直接输出其属性
void test01(){Person p1(10,10);//此时无法直接输出,需要重载 << 符号。利用全局函数重载 //cout<<p;//利用全局函数重载 << 后追加endl后就无法输出,因为重载的左移运算符返回的不是 cout 。将重载返回cout后即可使用 cout<<p1<<endl;
}
int main() {test01();return 0;
}
4.5.3 递增运算符重载
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//递增运算符重载
class MyInteger{friend ostream & operator<<(ostream &cout,MyInteger &p);
public:MyInteger(){m_num = 0;}//1.重载前置++运算符。返回引用是为了一直对一个数据递增。返回引用。 //不加引用的话,MyInteger mint; ++(++mint);执行后,默认的m_num是1,不是2; MyInteger& operator++(){//先进行++,再返回自身 m_num++;return *this; }//2.重载后置++运算符。返回值。因为中间使用了临时变量,若返回其引用,后续调用就是非法操作 //int 代表占位参数,可以用于区分前置和后置 MyInteger operator++(int){//先 记录当时结果MyInteger temp = *this;//后 递增m_num++;//返回未+1前的记录结果 return temp; }
private:int m_num;
};
//只能利用全局函数重载左移运算符
ostream & operator<<(ostream &cout,MyInteger &p){ //本质 opeartor<< (cout ,p) 简化 cout<< cout<<"m_num:"<<p.m_num;return cout;
}
//测试重载前置运算符
void test01(){MyInteger mint;cout<<++(++mint)<<endl;
}
//测试重载后置运算符
void test02(){MyInteger mint1;mint1++;cout<< mint1 <<endl;
}
int main() {test01();test02();return 0;
}
4.5.4 赋值运算符重载
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//赋值运算符重载
class Person{public:Person(int age){mage = new int(age);//创建变量到堆区 }~Person(){if(mage!=NULL){delete mage;mage = NULL;}}//重载赋值运算符Person& operator=(Person &p){//编译器是提供浅拷贝,但是对于堆区的数据存在问题 //mage = p.mage; //应该先判断是否有属性在堆区,若有则先释放干净,然后再深拷贝if(mage != NULL){delete mage;mage = NULL;}//深拷贝mage = new int(*p.mage); //返回对象本身return *this;} int *mage;
};
void test01(){Person p1(10);Person p2(16);Person p3(20);p3 = p2 = p1; //赋值操作,写析构函数中的 if(mage!=NULL){ delete mage;mage = NULL;} 后会崩。没写可以输出。 cout<<"p1的年龄为:"<<*p1.mage<<endl;cout<<"p2的年龄为:"<<*p2.mage<<endl;cout<<"p3的年龄为:"<<*p3.mage<<endl;
}
int main() {test01();return 0;
}
4.5.5 关系运算符重载
作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//关系运算符重载
class Person{
public:Person(string name,int age){mname = name;mage = age; }//重载==bool operator==(Person &p){if(this->mname == p.mname && this->mage == p.mage){return true;}return false;} //重载!=bool operator!=(Person &p){if(this->mname != p.mname || this->mage != p.mage){return false;}return true;} int mage;string mname;
};
void test01(){Person p1("tom",10);Person p2("tom",10);if(p1 == p2){cout<<"相等"<<endl; }else cout<<"不相等"<<endl;
}
int main() {test01();return 0;
}
4.5.6 函数调用运算符重载
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//函数调用运算符重载
class MyPrint{
public://重载()void operator()(string test){cout<<test<<endl;} int mage;string mname;
};
void MyPrint2(string t){cout<<t<<endl;
}
void test01(){MyPrint mp;mp("hello"); //因为使用起来非常类似于函数调用,因此称为仿函数MyPrint2("hello1"); //同上
}
int main() {test01();return 0;
}
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//函数调用运算符重载--仿函数
class MyAdd{
public:int operator()(int n1,int n2){return n1+n2;}
};
void test02(){MyAdd ad;int ret = ad(2,5); //仿函数 cout<<ret<<endl;//匿名函数对象。匿名对象(类名+()) cout<< MyAdd()(100,10)<<endl;
}
int main() {test02();return 0;
}
4.6 继承
继承是面向对象三大特性之一。
4.6.1 继承的用法
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//公共页面
class BasePage{
public:void header(){cout<<"头部信息"<<endl; }void footer(){cout<<"底部信息"<<endl; }void left(){cout<<"左侧信息"<<endl; }
};
//java页面 继承 公共页面
//语法: class 子类 : 继承方式 父类
//子类 也称 派生类
//父类 也称 基类
class Java:public BasePage{
public:void context(){cout<<"java内容"<<endl; }
};
//Python页面 继承 公共页面
class Py:public BasePage{
public:void context(){cout<<"Python内容"<<endl; }
};void test01(){Java ja;ja.header();ja.footer();ja.left();ja.context();Py p;p.header();p.footer();p.left();p.context();
}
int main() {test01();return 0;
}
继承好处:减少代码量
4.6.2 继承方式
继承语法:class 子类:继承方式 父类
继承方式一共有三种:
1.公共继承:
2.保护继承:
3.私有继承:
1.公有数据:类外可访问
2.保护数据:类外不可访问
3.私有数据:类外不可访问
4.6.3 继承中的对象模型
问题:从父类继承过来的成员,哪些属于子类对象中?
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//继承中的对象模型
class Base{
public:int ma;
protected:int mb;
private:int mc;
};
class Son : public Base{
public:int md;
};//利用开发人员命令提示工具查看对象模型,
//https://www.bilibili.com/video/BV1et411b73Z?p=136&vd_source=fa51e45d3ee148477e96ca9b174edea6 p136 14:55处
//跳转盘符 F:
//跳转文件路径 cd 具体路径下
//查看命名
// c1 /d1 reportSingleClassLayout类名 文件名
void test01(){// 输出 16。即父类中所有非静态成员属性都会被子类程序继承下去// 父类中私有成员属性,是被编译器给隐藏了,因此访问不到,但是确实被继承下去了 cout<<"size of Son = "<<sizeof(Son)<<endl;
}
int main() {test01();return 0;
}
4.6.4 继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构顺序是谁先谁后?
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//继承中构造和析构的顺序
class Base{
public:Base(){cout<<"Base构造函数!"<<endl; }~Base(){cout<<"Base析构函数!"<<endl; }
};
class Son : public Base{
public:Son(){cout<<"Son构造函数!"<<endl; }~Son(){cout<<"Son析构函数!"<<endl; }
};
void test01(){//Base b; //先创建父类在创建子类。即先父类构造,再有子类构造;然后是父类析构,子类析构(析构顺序与构造相反) Son s;
}
int main() {test01();return 0;
}
4.6.5 继承同名成员处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据?
1.访问子类同名成员,直接访问
2.访问父类同名成员 需要加作用域
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//继承中构造和析构的顺序
class Base{
public:Base(){ma = 10;}int ma; void func(){cout<<"Base-func调用"<<endl; }void func(int a){cout<<"Son-func(int a)调用"<<endl; }
};
class Son : public Base{
public:Son(){ma = 20;}int ma;void func(){cout<<"Son-func调用"<<endl; }};
//同名属性处理方式
void test01(){Son s;//访问子类自身的ma,直接访问 cout<<"Son ma:"<<s.ma<<endl;//访问父类的同名 ma,加作用域cout<<"Base ma:"<<s.Base::ma<<endl;
}
void test02(){Son s;s.func(); //直接调用,调用的是子类的同名成员//调用父类中同名的成员函数s.Base::func(); //若子类中出现和父类中同名的 成员函数,子类的同名成员会隐藏吊父类所有同名成员函数 //如果想访问父类中被隐藏的同名成员函数,需要加作用域s.Base::func(100);
}
int main() {test01();test02();return 0;
}
4.6.6 继承同名静态成员处理方式
问题:继承中通吗的静态成员函数在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一样。
1.访问子类同名成员 直接访问即可
2.访问父类同名成员 需要加作用域
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//继承中的同名静态成员处理方式
class Base{
public:static int ma;static void func(){cout<<"Base static void func()"<<endl;}
};
int Base::ma = 100;
class Son : public Base{
public:static int ma;static void func(){cout<<"Son static void func()"<<endl;} static void func(int a){cout<<"Son static void func(int a)"<<endl;}
};
int Son::ma = 200;
//1.静态同名属性
void test01(){//1.通过对象访问静态数据 Son s;cout<<"Son ma:"<<s.ma<<endl;cout<<"Base ma:"<<s.Base::ma<<endl;//2.通过类名访问cout<<"通过类名访问:"<<endl; cout<<"Son 下ma:"<<Son::ma<<endl;//第一个 ::代表通过类名方式访问 第二个::代表访问父类作用域下 cout<<"Base 下ma:"<<Son::Base::ma<<endl;
}
//2。同名静态函数成员
void test02(){//1.通过对象访问 Son s;s.func();s.Base::func();//2.通过类名访问Son::func(); Son::Base::func();//子类和父类出现同名函数,子类会将父类所有同名静态成员函数,也会隐藏掉父类所有童名成员函数//若要访问父类被隐藏同名函数,需要加作用域Son::Base::func(100);
}
int main() {test01();test02();return 0;
}
4.6.7 多继承语法
C++允许一个类继承多个类。
语法:class 子类 : 继承方式 父类1,继承方式 父类2 …
多几次可能会引发父类中同名成员出现,需要加作用域区分。
C++实际开发中不建议使用多继承。
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//多继承语法
class Base1{
public:Base1(){ma = 100;}int ma;
};
class Base2{
public:Base2(){mb = 200;}int mb;
};
//子类:继承base1 和 base2
class Son : public Base1,public Base2{
public:Son(){mc = 300;md = 400;}int mc;int md;
};void test01(){Son s;//输出16,四个成员变量 cout<<"sizeof(Son):"<<sizeof(s)<<endl;
}int main() {test01();return 0;
}
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
//多继承语法
class Base1{
public:Base1(){ma = 100;}int ma;
};
class Base2{
public:Base2(){ma = 200;}int ma;
};
//子类:继承base1 和 base2
class Son : public Base1,public Base2{
public:Son(){mc = 300;md = 400;}int mc;int md;
};void test01(){Son s;//输出16,四个成员变量 cout<<"sizeof(Son):"<<sizeof(s)<<endl;cout<<"Base 1 ma=:"<<s.Base1::ma<<endl;cout<<"Base 2 ma=:"<<s.Base2::ma<<endl;
}int main() {test01();return 0;
}