C++程序在执行时,将内存大方向划分为4个区域:
1.代码区:存放函数体的二进制代码,由操作系统进行管理
2.全局区:存放全局变量和静态变量以及常量
3.栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
4.堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
内存四区意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程。
在程序编译后,生成了exe可执行程序,未执行该程序前,分为两个区域:
1.代码区:存放CPU执行的机器指令
特点:
代码是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可;
代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
2.全局区:全局变量和静态变量存放在此
全局区还包含了常量区,字符串常量和其他常量也存放在此;
该区的数据在程序结束后由操作系统释放
//栈区
int a = 10;
int b = 10;
cout << "局部变量a的地址为:" << (int)&a << endl;
cout << "局部变量b的地址为:" << (int)&b << endl;
//全局区
//全局变量
int g_a = 10;
int g_b = 10;
cout << "全局变量g_a的地址为:" << (int)&g_a << endl;
cout << "全局变量g_b的地址为:" << (int)&g_b << endl;
//静态变量
static int s_a = 10;
static int s_b = 10;
cout << "静态变量s_a的地址为:" << (int)&s_a << endl;
cout << "静态变量s_b的地址为:" << (int)&s_b << endl;
//常量:字符串常量,const修饰的变量
cout << "字符串常量的地址为:" << (int)&"hello world" << endl;
//const修饰的全局变量,const修饰的局部变量
cout << "全局常量c_g_a地址为:" << (int)&c_g_a << endl;
cout << "全局常量c_g_b地址为:" << (int)&c_g_b << endl;
cout << "局部常量c_l_a地址为:" << (int)&c_l_a << endl;
cout << "局部常量c_l_b地址为:" << (int)&c_l_b << endl;
可以看出:全局变量,静态变量,字符串常量,全局常量的地址比较接近;而局部变量和局部常量的地址比较接近。由此可以得到下图:
栈区:
由编译器自动分配释放,存放函数的参数值,局部变量等
【注】:不要返回局部变量的地址,因为栈区开辟的数据由编译器自动释放
int* func(){
int a = 10;
return &a;
}
//main:
int* p = func();
cout << *p << endl;//10
cout << *p << endl;//其他
第一次可以正确的打印数字,是因为编译器做了保留,第二次打印的时候会出错(不再保留)。
堆区:由程序员分配释放,若程序员不释放,程序结束时由操作系统回收,在C++中主要利用【new在堆区开辟内存】。
int* func(){
//指针,本质是局部变量,放在栈上;指针保存的数据是放在堆区的
int* p = new int(10);
return p;
}
//main:
int* p = func();
cout << *p << endl;//10
int *func(){
//在堆区创建整型数据
//new返回的是 该数据类型的指针
int* p = new int(10);
return p;
}
void test01(){
int* p = func();
cout << *p <<endl;//10
//堆区的数据 由程序员管理开辟,释放
//利用关键字 delete 释放堆区的数据
delete p;
cout << *p << endl;//异常:读取访问冲突
}
堆区开辟数组及释放
void test02(){
int* arr = new int[10];//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;
}
即给变量起别名。
数据类型 &别名 = 原名;
(2个注意)
int a = 10;
int c = 10
//int &b;//错误,1.引用必须要初始化
int &b = a;
//int &b = c;//错误,2.一旦初始化后,就不可以更改了
b = 20;//此时a也是20
函数传参时,可以利用引用的技术让形参修饰实参,可以简化指针修改实参。
效果与地址传递一样。
void swap3(int& a, int& b) {//引用传递
int t = a;
a = b;
b = t;
}
int& test( ) {//返回局部变量的引用,1.不可返回
int a = 10;//局部变量,在栈区
return a;
}
int& test2( ) {
static int a = 10;//静态变量,在全局区,程序结束后系统释放
return a;
}
//main函数中:
int &ref1 = test();//会出错,见栈区讲解。
int &ref2 = test2();//ref是a的别名
test2() = 1000;//2.函数的调用作为左值(前提:函数的返回值是引用)
在C++内部实现是一个指针常量。引用,一旦初始化就不可以更改(指向不可变)。指针常量,指针类型的常量,指向不可变,指向的值可变。
主要用来修饰形参,防止误操作。
int& ref = 10;//错误,引用必须引一块合法的内存空间
const int& ref = 10;//正确,编译器将代码修改为 int t=10;const int &ref=t; 变量不可修改
参数不会被修改
void show(const int &a) {
//a = 100;
cout << "a=" << a << endl;
}
C++中,函数的形参列表中的形参是可以有默认值的。
返回值类型 函数名 (参数 = 默认值)
int fun(int a,int b = 10, int c = 20){ }
cout << fun(10) <cout << fun(10 , 20) < 【注】:
1.如果自己传入数据,就用自己的数据;如果没有,就用默认值。
2.如果某位置设置了默认值,那么从这个位置往后都要有默认值。
3.如果函数声明 &函数定义中的参数只能选其中一个有默认值。
C++函数的形参列表里可以有占位参数,用来占位,调用函数时必须填补该位置。占位参数可以有默认值。
返回值类型 函数名 (数据类型){}
void func(int a, int){}
func(10,10);
作用:函数名可以相同,提高复用性
满足条件:同一作用域下;函数名称相同;参数类型不同或个数不同或顺序不同。
【注】:
函数返回值不可以作为函数重载的条件(只有返回值不同,不满足函数重载条件)
//1.引用作为重载条件
void func(int &a){}
void func(const int &a){}
//main函数中:
int a = 10;
func(a);//调用第一个函数
func(10);//调用第二个函数
//int &a = 10;不合法 const int &a = 10;合法
//2.函数重载碰到默认参数
void func(int a,int b = 10){}
void func(int a){}
//main函数中:
func(10);//会出错,默认参数中设置了默认值在函数调用中即使缺省也可以正常调用。此时上述两个函数的参数情况均符合。
C++面向对象的三大特性:封装,继承,多态
C++认为万事万物皆为对象,对象上有其属性和行为。
具有相同性质的对象,可以称之为类。
【意义1】:将属性和行为作为一个整体,表现生活中的事物;
class 类名{访问权限:属性/行为};
//设计一个圆类,求圆的周长
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 << "圆的周长:" << calculateZC() << endl;
}
设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号
class Student{
public:
//类中的属性和行为:统一称为成员
string m_Name;//成员属性,成员变量
int m_Id;
void show(){//成员函数 成员方法
cout << "姓名:" << name << "学号:" << m_Id << endl;
}
};
//main:
Student s1;//实例化对象
s1.m_Name = "张三";
s1.m_Id = 1;
s1.show();
另一种赋值操作:
class Student{
public:
string m_Name;
int m_Id;
void show(){
cout << "姓名:" << name << "学号:" << m_Id << endl;
}
void setName(string name){
m_Name = name;
}
void setId(int id){
m_Id = Id;
}
};
//main
Student s1;//实例化对象
s1.setName("张三");
s1.setId(1);
s1.show();
【意义2】:将属性和行为加以权限控制。
类在设计的时候,可以把属性和行为放在不同的权限下,加以控制。
1.公共权限(public):成员在类内可以访问,类外可以访问
2.保护权限(protected):成员在类内可以访问,类外不可以访问。儿子可以访问父亲中的保护内容。
3.私有权限(private):成员在类内可以访问,类外不可以访问。儿子不可以访问父亲的私有内容。
class Person{
public:
string m_Name;
protected:
string m_Car;
private:
int m_Password;
public:
void func(){//类内都可以访问
m_Name = "张三";
m_Car = "拖拉机";
m_Password = 123456;
}
};
//main:
Person p1;
p1.m_Name = "里斯";//类内可以访问
//p1.m_Car = "奔驰";//类内不可访问
//p1.Password = 123;//类内不可访问
p1.func();//类内可以访问
唯一的区别在于默认的访问权限不同。
struct 默认权限为公共;class默认权限为私有。
【优点1】:可以自己控制读写权限
class Person{
public:
void setName(string name){//设置姓名
m_Name = name;
}
string getName(){//获取权限
return m_Name;
}//可读可写
int getAge(){
m_Age=0;//初始化为0
return m_Age;
}//只读
void setLover(string lover){
m_Lover = lover;
}//只写
private:
string m_Name;//可读可写
int m_Age;//只读
string m_Lover;//只写
};
//main:
Person p;
p.setName("张三");
cout << "姓名为:" << p.getName() << endl;
//p.m_Age = 18;//出错
cout << "年龄为:" << p.getAge() << endl;
p.setLover ("数据");
//cout << "爱人为:" << p.m_Lover() << endl;//出错,不可访问
【优点2】:对于写,可以检测数据的有效性
//以上述类中的成员m_Age为例,验证优点2
int getAge(){
return m_Age;
}//可读可写 年龄范围在0~150之间
void setAge(int age){
if(age < 0 || age > 150){
cout << "输入的年龄有误" << endl;
return;
}
m_Age = age;
}
利用成员函数判断两个立方体是否相等。
在class中的函数,就是成员函数。这个成员函数的参数值设置一个即可,因为成员函数可以直接访问成员变量。
点和圆的关系:
class Point{//点类
public:
void setX(int x){
m_X = x;
}
void getX(){
return m_X;
}
void setX(int y){
m_Y = y;
}
void getY(){
return m_Y;
}
private:
int m_X;
int m_Y;
};
class Circle{//圆类
public:
void setR(int r){
m_R = r;
}
void getR(){
return m_R;
}
void setCenter(Point center){
m_Center = center;
}
void getCenter(){
return m_Center;
}
private:
int m_R;
Point m_Center;//在类中,可以让另一个类作为成员
};
void isInCircle(Circle &c, Point &p){//判断点和圆的关系
int distance = (c.getCenter().m_X - p.getX()) *(c.getCenter().m_X - p.getX()) + (c.getCenter().m_Y - p.getY()) * (c.getCenter().m_Y - p.getY());
int rDistance = c.getR() * c.getR();
if(rDistance == distance){
cout << "点在圆上" << endl;
}
else if(rDistance > distance){
cout << "点在圆内" << endl;
}
else{
cout << "点在圆外" << endl;
}
}
//main:
Circle c;//创建圆
c.SetR(10);
Point center;
center.SetX(10);
center.SetY(0);
c.SetCenter(center);
Point p;//创建点
p.setX(10);
p.setY(10);
isInCircle(c, p);//判断关系
将类拆分成两部分,声明和实现
//point.h文件中:
#pragam once //防止头文件重复包含
#include
using namespace std;
class Point{
public:
void setX(int x);
int getX();
void setY(int y);
int getY();
private:
int m_X;
int m_Y;
}
源文件中都是函数实现:
//point.cpp文件中:
#include "point.h"
void Point::setX(int x){//在此文件中,这些函数为全局函数,但这些本来是成员函数,所以应该告知:这是Point作用域下的函数
m_X = x;
}
void Point::getX(){
return m_X;
}
void Point::setX(int y){
m_Y = y;
}
void Point::getY(){
return m_Y;
}
对象的初始化和清理是两个非常重要的安全问题。
一个对象或是变量没有初始状态,对其使用后果是未知;同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题。
C++利用了构造函数和析构函数解决上述问题,这两个杉树将会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供。【编译器提供的是空实现】。
【构造函数】:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
【析构函数】:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数:类名(){ }
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
class Person{
public:
Person(){//需要有作用域
cout << "Person构造函数的调用" << endl;
}//构造函数
~Person(){
cout << "Person析构函数的调用" << endl;
}
}
void test(){
Person p;//在栈上的数据,test执行完毕后,释放这个对象
}
析构函数:~类名(){ }
析构函数,没有返回值也不写void
函数名称与类名相同,在名称前加上符号~
析构函数不可以有参数,因此不可以发生重载
程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
两种分类方式:
1.按参数分–有参构造(默认构造)和无参构造;
2.按类型分–普通构造和拷贝构造;
class Person{
Person(){
cout << "Person的无参构造函数的调用" << endl
}
Person(int a){
age = a;
cout << "Person的有参构造函数的调用" << endl
}
Person(const Person &p){
//将传入的人身上的所以属性,拷贝到我身上
age = p.age;
cout << "Person的拷贝构造函数的调用" << endl
}//拷贝构造,其余都是普通构造
}
三种调用方式:括号法,显示法,隐式转换法。
【 注意】:
1.默认构造函数调用,不要加括号否则会被编译器认为是函数的声明。
2.不要利用拷贝构造函数。
初始化匿名对象,编译器会认为Person (p3) 是Person p3;在创建一个新的Person对象
void test(){
//括号法
Person p1;//默认构造函数调用,不要加括号否则会被编译器认为是函数的声明
Person p2(10);//有参构造函数
Person p3(p2);//拷贝构造函数
//显示法
Person p4;
Person p5 = Person(10);//有参构造
Person p6 = Person(p2);//拷贝构造
//Person(10);//匿名对象,当前执行结束后,系统会立即回收匿名对象
//隐式转换法
Person p7 = 10;//相当于 Person p7 = Person(10);有参构造
Person p8 = p7;//拷贝构造函数
}
1.使用一个已经创建完毕的对象来初始化一个新对象;
2.值传递的方式给函数参数传值;
3.以值方式返回局部对象;
class Person{
public:
Person(){
cout << "Person默认构造函数调用" << endl;
}
Person(int age){
m_Age = age;
cout << "Person有参构造函数调用" << endl;
}
Person(const Person &p){
m_Age = p.age;
cout << "Person拷贝构造函数调用" << endl;
}
~Person(){
cout << "Person析构函数调用" << endl;
}
int m_Age;
};
void test1(){//1.使用一个已经创建完毕的对象来初始化一个新对象
Person p1(20);//默认
Person p2(p1);//拷贝
}
void doWork(Person p){//2.值传递的方式给函数参数传值
//值传递:拷贝一个临时副本(拷贝构造函数)
//实参传给形参时,调用一个拷贝构造函数
}
void test2(){
Person p;//默认
doWork(p);//拷贝
}
Person doWork2(){//3.以值方式返回局部对象
Person p1;//默认
return p1;//拷贝
}
void test3(){
Person p = doWork2();
}
默认情况下,C++编译器至少给一个类添加3个函数:
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
1.如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
2.如果用户定义拷贝构造函数,C++不会再提供其他构造函数
class Person{
public:
Person(){
cout << "Person的默认构造函数调用" << endl;
}
Person(int age){
m_Age = age;
cout << "Person的有参构造函数调用" << endl;
}
Person(const Person &age){
m_Age = p.age;
cout << "Person的拷贝构造函数调用" << endl;
}//即使没有这个函数,Person p2(p1);也会
~Person{
cout << "Person的析构函数调用" << endl;
}
int m_Age;
};
void test(){
Person p;
p.m_Age = 18;
Person p2(p);//即使没有Person(const Person &age),也能得到p2的年龄是18。因为C++至少给一个类添加3个函数
cout << "p2的年龄为:" << p2.m_Age << endl;
}
void test2(){
//Person p;//没有Person(),会出错。因为我们定义了有参,编译器将不会提供无参
Person p(28);
Person p2(p);//没有默认构造函数,但编译器仍会提供拷贝构造函数
cout << "p2的年龄为:" << p2.m_Age << endl;//28
}
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
析构函数:堆区内存的释放
class Person{
public:
//默认构造函数
Person(int age, int height){
m_Age = age;
m_Height = new int (height);
}
~Person(){
//析构函数,将堆区开辟数据做释放操作
if(m_Height != NULL){
delete m_Height;
m_Height = NULL;//防止野指针出现,置空
}//会报错
}
int m_Age;
int* m_Height;
};
void test(){
Person p1(18,160);
Person p2(p1);
}
Person(const Person &p){
cout << "Person拷贝构造函数调用" << endl;
m_Age = p.m_Age;
//m_Height = p.m_Height;//编译器默认实现的代码(浅拷贝)
//深拷贝
m_Height = new int(*p.m_Height);
}
语法:
构造函数():属性1(值1),属性2(值2)…{ }
class Person{
public:
/*//传统初始化操作
Person(int a, int b, int c){
m_A = a;
m_B = b;
m_C = c;
}*/
//初始化列表初始化属性
Person():m_A(10),m_B(10),m_C(10){}//值固定
/*Person(int a, int b, int c):m_A(a),m_B(b),m_C(c){
//可以更加灵活的赋值
}*/
int m_A;
int m_B;
int m_C;
};
void test(){
//Person p(10,20,30);
Person p;
//Person p(30,20,10);//30 -> int a -> a -> m_A
}
C++类中的成员可以是另一个类的对象,我们称之为:对象成员
class A{};
class B{
A a;//B类中有对象A作为成员,A为对象成员
};
创建B对象时,A与B的构造和析构的顺序是谁先是谁后?
class Phone{
public:
Phone(string pName){
m_PName = pName;
}
string m_PName;
};
class Person{
public:
//Phonr m_Phone = pName;隐式转换法
Person(string name,string pName):m_Name(name),m_Phone(pName){}
string m_Name;
Phone m_Phone;
};
void test(){
Person p("张三","苹果MAX");
}
当其他类对象作为本类成员,构造时候先构造类对象,再构造自身;析构顺序与构造相反。
在成员变量和成员函数前加上关键字static,就是静态成员。
1.静态成员变量:
所有对象共享同一份数据;在编译阶段分配内存;类内声明,类外初始化
2.静态成员函数:
所有对象共享同一个函数;静态成员函数只能访问静态成员变量
静态成员变量不属于某个对象,因为所有对象都共享同一份数据
class Person{
public:
static int m_A;//类内声明
private:
static int m_B;
};
int Person::m_A = 100;//类外初始化
int Person::m_A = 200;
void test(){
Person p;
cout << p.m_A << endl;//100
Person p2;
p2.m_A = 200;
cout << p.m_A << endl;//200,共享同一份数据
}
void test2(){//两种访问方式
//1.通过对象进行访问
Person p;
cout << p.m_A << endl;
//2.通过类名访问
cout << Person::m_A << endl;
//cout << Person::m_B << endl;//出错,静态成员变量有访问权限
}
静态成员函数
class Person{
public:
static void func(){
m_A = 100;//静态成员函数可以访问静态成员变量
//m_B =200;//出错,无法区分是哪个是哪个对象的m_B
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 = 0;
void test(){//两种访问方式
//1.通过对象访问
Person p;
p.func();
//2.通过类名访问
Person::func();
//Person::func2();//出错,静态成员函数有访问权限
}
在C++中,类内的成员变量和成员函数分开存储。
只有非静态成员变量才属于类的对象上。
空类:
class Person{ };
void test(){
Person p;
//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置,每个空对象也应该有一个独一无二的内存地址
cout << "size of p = " << sizeof(p) << endl;//1
}
非静态成员变量:
class Person{
int m_A;非静态成员变量,属于类的对象上
};
void test1(){
Person p;
cout << "size of p = " << sizeof(p) << endl;//4
}
非静态成员变量 + 静态成员变量:
class Person{
int m_A;
static int m_B;//静态成员变量,不属于类的对象上
};
int Person::m_B = 0;
void test2(){
Person p;
cout << "size of p = " << sizeof(p) << endl;//4
}
非静态成员变量 + 静态成员变量 + 非静态成员函数:
class Person{
int m_A;
static int m_B;
void func(){ }//非静态成员函数,不属于类的对象上
};
int Person::m_B = 0;
void test2(){
Person p;//成员变量和成员函数,是分开存储的
cout << "size of p = " << sizeof(p) << endl;//4
}
非静态成员变量 + 静态成员变量 + 非静态成员函数 + 静态成员函数:
class Person{
int m_A;
static int m_B;
void func(){ }
static void func(){}//静态成员函数,不属于类的对象上
};
int Person::m_B = 0;
void test2(){
Person p;
cout << "size of p = " << sizeof(p) << endl;//4
}
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。
通过提供特殊的对象指针—this指针,来区分是哪个对象调用自己的。【this指针指向被调用的成员函数所属的对象。】
this指针是隐含每一个非静态成员函数内的一种指针。不需要定义,【直接使用】即可。
this指针的用途:
当形参和成员变量同名时,可用this指针来区分
在类的非静态成员函数中返回对象本身,可使用return *this
class Person{
public:
Person(int age){
//age = age;//会出错
this->age = age;//指向的是被调用的成员函数 所属的对象(p1在调用Person(int age)),this指向p1
}
/*void PersonAddAge(Person &p){
this->age += p.age;
}*/
//若返回类型为Person,结果是20。第一次调用后,拷贝了新的数据,创建新的对象
Person& PersonAddAge(Person &p){
this->age += p.age;
//this指向p2的指针,而*this指向的就是p2这个对象本体
return *this;
}
int age;
};
//1.解决名字冲突
void test(){
Person p1(18);
cout << "p1的年龄:" << p1.age << endl;
}
//链式编程
void test(){
Person p1(10);
Person p2(10);
//p2.PersonAddAge(p1);
//多次相加p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1)会出错
//因为PersonAddAge的返回类型是void,返回类型是p2才可
//链式
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout << "p2的年龄:" << p2.age << endl;//20,40
}
class Person{
public:
void showClassName(){
cout << "this is Person class" << endl;
}
void showPersonAge(){
//因为传入的指针是NULL
if(this == NULL){
return;
}
cout << "age = " << this->m_Age << endl;
}
int m_Age;
};
void test(){
Person* p = NULL;
p->showClassName();//没问题
//p->showPersonAge();//出错,this是空指针
}
常函数:
成员函数后加const,我们称之为常函数(修饰的是this指针,指向的值也不可以修改)
常函数内不可以修改成员属性
成员属性声明时加关键字mutable,在常函数中依然可以修改
常对象:
声明对象前加const,称之为常对象
常对象只能调用常函数
class Person{
public:
void showPerson(){
m_A = 100;//可以修改
this->m_A = 200;//可以修改
}
//常函数
void showPerson1() const{
this->m_B = 100;
//this = NULL;//出错,指向不可修改
}
/* //this指针本质:指针常量,指针的指向是不可以修改的
void showPerson() const{//相当于const Person* const this;
this->m_A = 100;//不可以修改
}
*/
int m_A;
mutable int m_B;
};
void test1(){
Person p;
p.showPerson();
}
//常对象
void test2(){
const Person p;//常对象
//p.m_A = 100;//出错
p.m_B = 100;//可以修改
p.showPerson1();//常对象只能调用常函数
//p.showPerson();//出错,普通成员函数可修改属性
}
关键字:friend
目的:让一个函数或者类 访问另一个类中私有成员。
有3种实现
friend void goodGay(Building *building);
class Building{
friend void goodGay(Building *building);//此时可以访问private成员
public:
Building(){
m_SittingRoom = "客厅";
m_BedRoom ="卧室";
}
public:
string m_SittingRoom;//客厅
private:
string m_BedRoom;//卧室
};
//全局函数
void goodGay(Building *building){
cout << "好朋友全局函数 正在访问:" << building->m_SittingRoom <<endl;
cout << "好朋友全局函数 正在访问:" << building->m_bedRoom <<endl;//不可以访问->ok
}
void test(){
Building building;
goodGay(&building);
}
friend class Building;
类外成员函数:【类名::函数名】
class Building;
class goodGay{
public:
goodGay();
void visit();//该函数访问Building中的属性
private:
Building* building;
};
class Building{
friend class goodGay;//类做友元,可以访问private
public:
string m_SittingRoom;//客厅
private:
string m_BedRoom;//卧室
};
//类外成员函数
Building::Building(){
m_SittingRoom = "客厅";
m_BedRoom ="卧室";
}
goodGay::goodGay(){
building = new Building;//堆区创建建筑物对象,Building* building指向new出的对象
}
goodGay::void visit(){
cout << "好朋友全局函数 正在访问:" << building->m_SittingRoom <<endl;
cout << "好朋友全局函数 正在访问:" << building->m_bedRoom <<endl;//出错->ok
}
friend void GoodGay::visit();
class Building;
class GoodGay{
public:
GoodGay();
void visit();//可以访问private
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;//堆区创建建筑物对象,Building* building指向new出的对象
}
goodGay::void visit(){
cout << "好朋友全局函数 正在访问:" << building->m_SittingRoom <<endl;
cout << "好朋友全局函数 正在访问:" << building->m_bedRoom <<endl;//出错->ok
}
goodGay::void visit2(){
cout << "好朋友全局函数 正在访问:" << building->m_SittingRoom <<endl;
cout << "好朋友全局函数 正在访问:" << building->m_bedRoom <<endl;//出错
}
对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
1.不要滥用运算符。
2.对于内置的数据类型的表达式的运算符是不可能改变的(自定义类型可以,但int或double之类的不可以)。
实现两个【自定义】数据类型相加。
class Person{
public:
//1.成员函数重载+
Person operator+(Person &p){
Person temp;//Person p3 = p1.operator+(p2);本质
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
int m_A;
int m_B;
};
//2.全局函数重载+
Person operator+(Person &p1, Person &p2){
Person temp;//Person p3 = operator+(p1, p2);本质
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
//函数重载
Person operator+(Person &p1, int num){
Person temp;//Person p3 = operator+(p1, num);本质
temp.m_A = p1.m_A + num;
temp.m_B = p1.m_B + num;
return temp;
}
void test(){
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1 + p2;//简化形式
cout << "p3 = " << p3.m_A << endl;//20
cout << "p3 = " << p3.m_B << endl;//20
}
可以输出自定义的数据类型。
class Person{
public:
void operator<<(cout){}//p.operator<<(cout) 简化为 p<
//所以不会用成员函数来重载
int m_A;
int m_B;
};
ostream &operator<<(ostream &cout, Person p){//operator(cout, p)简化为cout<
cout << "m_A = " << p.m_A << "m_B = " << p.m_B;
return cout;
}//ostream只能有一个,所以加&
void test(){
Person p;
p.m_A = 10;
p.m_B = 10;
cout << p.m_A << endl;
}
实现自己的整型数据。
//自定义整型
class MyInteger{
friend ostream &operator<<(ostream &cout, MyInteger myint);
public:
MyInteger(){
m_Num = 0;
}
//重载前置++运算符
MyInteger &operator++(){//返回引用是为了一直对一个数据进行递增操作
m_Num++;//先++运算,再返回自身
return *this;
}
//重载后置++运算符,temp是局部变量,若返回引用后续会非法操作
MyInteger operator++(int){//int:占位参数,用于区分前置后置递增
MyInteger temp = *this;//先记录当前结果,再递增,最后将记录结果返回
m_Num++;
return temp;
}
private:
int m_Num;
};
ostream &operator<<(ostream &cout, MyInteger myint){
cout << myint.m_Num;
return cout;
}
void test(){
MyInteger myint;//自定义类型,需要重载 左移运算符
cout << ++(++myint) << endl;
cout << myint << endl;
}
void test2(){
MyInteger myint;
cont << myint++ << endl;
cout << myint << endl;
}
C++编译器至少给一个类添加4个函数:
默认构造函数,析构函数,拷贝构造函数。
赋值运算符operator=,对属性进行值拷贝
class Person{
public:
Person(int age){
m_Age = new int(age);//开辟到堆区,手动释放
}
~Person(){
if(m_Age !=NULL){
delete m_Age;
m_Age = NULL;
}
}
int* m_Age;
};
void test(){
Person p1(18);
cout << "p1的年龄:" << *p1.m_Age << endl;//18
Person p2(20);
cout << "p2的年龄:" << *p2.m_Age << endl;//20
p2 = p1;//赋值操作
cout << "p2的年龄:" << *p2.m_Age << endl;//18
}
//重载 赋值运算符,在class Person中
Person& operator=(Person &p){
//应该先判断是否有属性在堆区,如果有,先释放干净,然后再深拷贝
if(m_Age != NULL){
delete m_Age;
m_Age = NULL;
}
m_Age = new int(*p.m_Age);//深拷贝
return *this;//返回对象本身
}
可以让两个自定义类型对象进行对比操作。
class Person{
public:
Person(string name, int age){
m_Name = name;
m_Age = age;
}
//重载 ==号
bool operator==(Person &p){
if(this->m_Name == p.m_Name && this->m_Age == p.m_Age){
return true;
}
else{
return false;
}
}
string m_Name;
int m_Age;
};
void test(){
Person p1("Tom", 18);
Person p1("Tom", 18);
if(p1 == p2){
cout << "相等" << endl;
}
else{
cout << "不相等" << endl;
}
}
函数调用运算符()也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数。
class MyPrint{
public:
//重载函数调用运算符
void operator()(string test){
cout << test << endl;
}
};
void test(){
MyPrint myPrint;
myPrint("hello world");//由于重载后使用的方式非常像函数的调用,因此成为仿函数
}
仿函数没有固定写法,非常灵活。
class MyAdd{
public:
int operator()(int num1, int num2){
return num1 + num2;
}
};
void test(){
MyAdd myAdd;
int ret = myAdd(100,100);
//匿名函数对象
cout << MyAdd()(100,100) << endl;
}
继承,是面向对象三大特性之一
减少重复代码
class Java:public BasePage(子类/派生类:父类/基类)
//普通页面实现
class Java{
public:
void header(){
cout << "首页、公开课、登录...(公共头部)" << endl;
}
void footer(){
cout << "帮助中心、交流合作...(公共底部)" << endl;
}
void left(){
cout << "Java、Python...(公共分类列表)" << endl;
}
void content(){
cout << "Java学科视频" << endl;
}
};
//不同页面的内容只有content不同,重复代码过多。
void test(){
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
}
class BasePage{//继承
public:
void header(){
cout << "首页、公开课、登录...(公共头部)" << endl;
}
void footer(){
cout << "帮助中心、交流合作...(公共底部)" << endl;
}
void left(){
cout << "Java、Python...(公共分类列表)" << endl;
}
};
class Java:public BasePage{
public:
void content(){
cout << "Java学科视频" << endl;
}
};
利用【开发人员命令提示工具】查看对象模型:
跳转盘符 F:
跳转文件路径 cd 具体路径
查看命名 cl /d1 reportSingleClassLayout类名 文件名(Tab自动补齐文件名)
构造父类–>构造子类;析构则相反
创建子类对象时,父类对象也会被创建。
父类与子类 出现同名的成员:
子类:直接访问;父类:加作用域
若子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有的同名成员函数
非静态:
class Base{
public:
void func(){
cout << "BASE" << endl;
}
void func(int a){
cout << "base" << endl;
}
};
class Son:public Base{
public:
void func(){
cout << "SON" << endl;
}
};
void test(){
Son s;
s.func();//SON
s.Base::func();//BASE
//s.func(100);//出错
s.Base::func(100);
}
静态成员:与非静态一样
通过【类名】和【对象】访问
类名:Son::Base::m_A(通过类名的方式,访问父类作用域下的m_A)
一个类继承多个类
class 子类:继承方式 父类1,继承方式 父类2,…
多继承可能会引发父类中有同名成员出现,需要加作用域区分
实际开发中不建议用多继承
class Base1{ };
class Base2{ };
class Son:public Base1,public Base2{ };
菱形继承/钻石继承:
两个派生类继承同一个基类,又有某个类同时继承这个派生类。
class Animal{
public:
int m_Age;
};
class Sheep:public Animal{};
class Tuo:public Animal{};
class YT:public Sheep,public Tuo{};
void test(){
YT st;
//st.m_Age = 18;//出错,二义性
st.Sheep::m_Age = 28;
st.Tuo::m_Age = 28;//数据有一份即可,但现在有两份
}
利用【虚继承】解决菱形继承的问题【virtual】,Animal类为虚基类。
class Sheep:virtual public Animal{};
class Tuo:virtual public Animal{};
cout << st.age << endl;
在函数调用后面 + “:父类函数名”,即完成了该函数的调用,再写自己的函数即可;
class Base {
...
public:
Base(int x, int y) {
this->x = x;
this->y = y;
}
...
};
class Sub : public Base {
private:
int z;
public:
Sub(int x, int y, int z) :Base(x,y ) {//父类函数的调用,xyz的有参构造
this->z=z;
}
int getZ() {
return z;
}
int calculate() {
return Base::getX() * Base::getY() * this->getZ();//父类函数的调用 + 作用域
}
};
面向对象的三大特性之一。
静态多态:函数重载和运算符重载属于静态多态,复用函数名
动态多态:派生类和虚函数实现运行时多态(有继承关系;子类重写父类函数)
区别:
静态多态的函数地址早绑定,编译阶段确定函数地址
动态多态的函数地址晚绑定,运行阶段确定函数地址
class Animal{
public:
void speak(){
cout << "动物在说话" << endl;
}
};
class Cat:public Animal{
public:
void speak(){
cout << "小猫在说话" << endl;
}
};
void doSpeak(Animal &anmial){//使用:父类的指针或引用 执行子类对象
animal.speak();
}
void test(){
Cat cat;
doSpeak(cat);
}
运行结果:动物在说话。doSpeak,地址早绑定,在编译阶段确定地址。如果想执行猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定。
//虚函数
virtual void speak(){};//加在父类中,小猫在说话
普通写法
class Calculator{
public:
int getResult(string oper){
if(oper == "+"){
return m_Num1 + m_Num2;
}
else if(oper == "-"){
return m_Num1 - m_Num2;
}
else if(oper == "*"){
return m_Num1 * m_Num2;
}//若想扩展新的功能,需要修改源码
}
int m_Num1;
int m_Num2;
};
void test(){
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 10;
cout << c.m_Num1 << "+" << c.m_Num2 << "=" << c.getResult("+") << endl;
}
多态:代码量变大,但组织结构清晰,可读性强,对于前期和后期的扩展和维护性高
class AbstractCalculator{//计算器抽象类
public:
int getResult(){
return 0;
}
int m_Num1;
int m_Num2;
};
class AddCalculator:public AbstractCalculator{
public:
virtual int getResult(){
return m_Num1 + m_Num2;
}
};
class SubCalculator:public AbstractCalculator{
public:
int getResult(){
return m_Num1 - m_Num2;
}
};
class MulCalculator:public AbstractCalculator{
public:
int getResult(){
return m_Num1 * m_Num2;
}
};
void test(){
AbstractCalculator *abc = new AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
delete abc;
abc = new SubCalculator;//数据释放,类型没有变
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getResult() << endl;
}
通常父类中虚函数的实现是无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为【纯虚函数】。
virtual 返回值类型 函数名(参数列表)= 0;
当类中有了纯虚函数,这个类也称为抽象类
特点:
1.无法实例化对象(Base b;创建对象)
2.子类必须重写抽象类中的纯虚函数,否则也属于抽象类
class AbstractDrinking{
public:
virtual void Boil() = 0;
virtual void Brew() = 0;
virtual void PourInCup() = 0;
virtual void PutSomething() = 0;
void makeDrink(){
Boil();
Brew();
PourInCup();
PutSomething();
}
};
class Coffee:public AbstractDrinking{
public:
virtual void Boil(){
cout << "煮水" << endl;
}
virtual void Brew(){
cout << "冲泡" << endl;
}
virtual void PourInCup(){
cout << "倒入" << endl;
}
virtual void PutSomething(){
cout << "加入" << endl;
}
};
void doWork(AbstractDrinking *abs){
abs->makeDrink();//一个接口多种形态
delete abs;
}
void test(){
doWork(new Coffee);
}
多态使用时,若子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。因此,要将父类中的析构函数改为虚析构或纯虚析构。
共性:
可以解决父类指针释放子类对象
都要有具体的函数实现
区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构–virtual ~类名(){ }
纯虚析构–virtual ~类名() = 0; 类名::~类名(){ }需要声明和实现
案例描述:电脑主要组成部件为CPU,显卡,内存条。将每个零件封装出抽象基类,并提供不同放入厂商生产不同的零件。创建电脑类提供让电脑工作的函数,并调用每个零件工作的接口。测试时组装三台不同的电脑进行工作。
//零件类
class CPU{//抽象CPU类
public:
virtual void calculate() = 0;//抽象计算函数
};
class VideoCard{//抽象显卡类
public:
virtual void display() = 0;//抽象显示函数
};
class Memory{//抽象内存条类
public:
virtual void storage() = 0;//抽象存储函数
};
//电脑类
Class Computer{
public:
Computer(CPU *cpu, VideoCard *vc, Memory *mem){
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
}
void work(){//提供工作的函数
m_cpu->calculate();//让零件工作起来,调用接口
m_vc->display();
m_mem->storage();
}
~Computer(){
if(m_cpu !=NULL){
delete m_cpu;
m_cpu = NULL;
}
if(m_vc !=NULL){
delete m_vc;
m_vc = NULL;
}
if(m_mem !=NULL){
delete m_mem;
m_mem = NULL;
}
}
private:
CPU *m_cpu;//CPU的零件指针
VideoCard *m_vc;//显卡零件指针
Memory *m_mem;//内存条的零件指针
};
//具体厂商
class IntelCPU:public CPU{
virtual void calculate(){
cout << "Intel的CPU开始计算了" << endl;
}
};
class IntelVideoCard:public VideoCard{
virtual void display(){
cout << "Intel的显卡开始显示了" << endl;
}
};
class IntelMemory:public Memory{
virtual void storage(){
cout << "Intel的内存条开始存储了" << endl;
}
};
class LenovoCPU:public CPU{
virtual void calculate(){
cout << "Lenovo的CPU开始计算了" << endl;
}
};
class LenovoVideoCard:public VideoCard{
virtual void display(){
cout << "Lenovo的显卡开始显示了" << endl;
}
};
class LenovoMemory:public Memory{
virtual void storage(){
cout << "Lenovo的内存条开始存储了" << endl;
}
};
void test(){
CPU *intelCPU = new IntelCPU;
VideoCard *intelCard = new IntelVideoCard;
Memory *intelMem = new IntelMemory;
Computer *computer1 = new Computer(*intelCPU, *intelCard, *intelMem);
computer1->work();
delete computer1;//第一台电脑
//第二台电脑组装
Computer *computer2 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory);
computer2->work();
delete computer2;
}
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放。通过文件可以将数据持久化。
头文件:
二种类型:
1.文本文件:以文本的ASCII码形式存储在计算机中
2.二进制文件:以文本的二进制形式存储在计算机中,用户一般不能直接读懂
操作文件的三大类:
1.ofstream:写操作
2.ifstream:读操作
3.fstream:读写操作
写文件步骤:
1.包含头文件:#include
2.创建流对象:ofstream ofs;
3.打开文件:ofs.open(“文件路径”,打开方式);//不指定路径,写在项目同一路径下
4.写数据:ofs << “写入的数据”;
5.关闭文件:ofs.close();
2和3可合并:ofstream ofs(“文件路径”,打开方式);
打开方式 | 解释 |
---|---|
ios::in | 为读文件而打开文件 |
ios::out | 为写文件而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 追加方式写文加 |
ios::trunc | 如果文件存在先删除,再创建 |
ios::binary | 二进制方式 |
文件打开方式可以配合,利用【|】操作符。
用二进制方式写文件:【ios::binary | ios::out】
读文件步骤:
1.包含头文件:#include
2.创建流对象:ifstream ifs;
3.打开文件:ifs.open(“文件路径”,打开方式);
判断是否打开成:if(!ifs.is_open()){cout << “文件打开失败” << endl;return;}
4.读数据:四种方式读取
5.关闭文件:ifs.close();
//读数据1:
char buf[1024] = {0};
while( ifs >> buf){
cout << buf << endl;
}
//读数据2:
char buf[1024] = {0};
while(ifs.getline(buf, sizeof(buf))){
cout << buf << endl;
}
//读数据3:
string buf;
while(getline(ifs, buf)){
cout << buf << endl;
}
//读数据4:
char c;
while((c = ifs.get()) !=EOF){
cout << c;
}
以二进制的方式对文件进行读写操作,打开方式要指定为 ios::binary
二进制写文件主要利用流对象调用成员函数write
函数原型:ostream& write(const char* buffer, int len);
参考解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
与文本文件的写数据不同,其余都一样。【调用函数,写数据。Person p = { … };】
读文件 read
函数原型:istream& read(char* buffer, int len);
参考解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
读数据:Person p;调用read函数;cout