程序内存模块
内存区四区
-
代码区:存放二进制代码,由操作系统进行管理
-
全局区:存放全局变量和静态变量、常量
-
栈区:由编译器自动分配释放,存放函数的参数值、局部变量等
-
堆区:由程序员分配和释放,若程序员没有释放,则程序结束时操作系统回收
意义:
不同的区域存放数据,赋予不同的生命周期,给我们更大的灵活编程
1.1 程序运行前
在程序编译后,生成了 exe 可执行文件,未执行该程序前分为两个区域:
代码区:
存放 CPU 执行的机器指令
代码区是共享的(一份代码可被重复执行)、只读的(防止程序意外的修改指令)
全局区:
全局变量、静态变量
常量区:字符串常量、其他常量
数据在程序结束后由操作系统释放
#include <iostream>
#include <cstdint> // 为了使用 uintptr_t
using namespace std;
//g-lobal c-const l-local
// 全局变量
int g_a = 10;
// 全局常量
const int c_g_a = 10;
int main() {
cout << "全局变量地址 = " << reinterpret_cast<uintptr_t>(&g_a) << endl;
// 局部变量
int a = 10;
cout << "局部变量地址 = " << reinterpret_cast<uintptr_t>(&a) << endl;
// 静态变量
static int s_a = 10;
cout << "静态变量地址 = " << reinterpret_cast<uintptr_t>(&s_a) << endl;
// 字符串常量("hello world")
const char* str = "hello world";
cout << "字符串常量地址 = " << reinterpret_cast<uintptr_t>(str) << endl;
// const 修饰的变量
// 1、const 修饰全局变量
cout << "全局常量地址 = " << reinterpret_cast<uintptr_t>(&c_g_a) << endl;
// 2、const 修饰局部变量
const int c_l_a = 10;
cout << "局部常量地址 = " << reinterpret_cast<uintptr_t>(&c_l_a) << endl;
return 0;
}
总结
| 非全局区 | 全局区 |
|---|---|
| 局部变量、局部常量 | 全局变量、静态变量、字符串常量、全局常量 |
1.2 程序运行后
栈区:
不要返回局部变量的地址
int* func()
{
int a = 10;//数据存放于栈区,函数执行完自动释放
return &a;
}
堆区:
new 开辟内存
int* func()
{
int *p = new int(10);//保存在堆区,由程序员释放
return p;
}
1.3new 操作符
int* func()
{
//new 返回该数据类型的指针
//int *p = new int(10) == int *p = new int; *p = 10;
//创建整型变量
int *p = new int(10);
return p;
}
void test()
{
int *p = func();
//释放堆区数据,整型
delete p;
//创建数组
int *arr = new int[10];
//释放数组
delete[] arr;
}
引用
2.1 引用的基本使用
**作用:**给变量起别名
**语法:**数据类型 &别名 = 原名
int a = 10;
int &b = a;
2.2 引用的注意事项
引用必须初始化
int& b; //错误
引用一旦初始化就不可以更改
如果已经执行了 int& b = a; 不可以执行&b = c;
2.3 引用做函数参数
**作用:**函数传参时,可以利用引用的技术让形参修饰实参
**优点:**可以简化指针修改实参
//引用传递
void func(int& a, int& b)
{
int temp = a;
a = b;
b = a;
}
int main()
{
swap(a, b);
}
2.4 引用做函数的返回值
不要返回局部变量的引用
**用法:**函数的调用可以作为左值
int& func()
{
int a = 10; //数据在栈区
return a;
}
int& funb()
{
static int a = 10; //数据在全局区
return a;
}
int main()
{
int& c = func();
int& b = funb();
funb() = 1000; //funb()返回 a 的引用,即 a = 1000,同时 b 是 a 的引用,即 b = 1000
}
2.5 引用的本质
**本质:**在 c++内部实现是一个指针常量
int& b = a; //int* const b = &a;
b = 20; //*b = 20;
2.6 常量引用
**作用:**修饰形参,防止误操作
//引用必须引一块合法的内存空间
int& b = 10; //错误,10 是常量
const int& b = 10; //正确,即 int temp = 10; int &b = temp;
//但是不能修改值
b = 20; //错误
void print(const int& a)
{
//a = 100; //错误,const 修饰之后不可改值
cout<<"a = "<<a<<endl;
}
int main()
{
int a = 10;
print(a);
}
函数
3.1 函数默认参数
**语法:**返回值类型 函数名 (参数 = 默认值){}
注意:
1.如果从某个位置开始有了默认参数,那么这个位置往后,都必须有默认值
//从 b 开始往后都必须有默认值
void fun(int a, int b = 10, int c = 10, int d = 10)
2.如果函数声明有默认参数,函数实现就不能有默认参数(声明和实现只能一个有默认值)
int fun(int a, int b = 10);
int fun(int a, int b)
{
a += b;
}
3.2 函数重载
**作用:**函数名相同,提高复用性
条件:
-
同一个作用域下(全局/局部)
-
函数名相同
-
函数参数类型不同或个数不同或顺序不同
函数的返回值不可以作为函数重载的条件
注意:
1、引用作为重载条件
void func(int &a) //1
void func(const int &a) //2
int main()
{
int a;
func(a); //1
func(10); //2
}
2、函数重载碰到函数默认参数
void func(int a, int b = 10)
void func(int a)
int main()
{
int a;
func(a); //错误,出现二义性,尽量避免
}
类和对象 c++面向对象三大特征:封装、继承、多态
**eg:**人可以作为对象,属性:姓名、年龄、身高...,行为:走、跑、跳...,具有相同性质的对象,我们可以抽象为类,如:人属于人类、车属于车类
4.1 封装
4.1.1 封装的意义
-
将属性和行为作为一个整体
-
将属性和行为加以权限控制
**语法:**class 类名{ 访问权限: 属性 / 行为 };
const double pi = 3.14;
class circle
{
//类中的属性和行为统一称为成员
//属性可以称作成员属性、成员变量
//行为可以称为成员函数、成员方法
//访问权限
public:
//圆的属性
int r;
//行为(获取圆的周长)
double cal()
{
return 2*pi*r;
}
};
int main()
{
//创建圆类对象(实例化)
circle c1;
//给属性赋值
c1.r = 10;
cout<<"圆的周长:"<<c1.cal();
}
- 访问权限:
1、public(公共):成员 类内和类外可以访问
2、protected(保护):成员 类内可以访问,类外不可以
3、private(私有):成员 类内可以访问,类外不可以,在继承中,儿子不可以访问父亲的内容
class person
{
private:
int password;
public:
string name;
protected:
string car;
public:
void func()
{
name = "zhang";
car = "icar";
password = 123;
}
};
int main()
{
person p;
p.name = "li";
//保护和私有权限,类外都不可以访问
p.car = "bcar"; //错误
p.password = 234; //错误
}
4.1.2struct 和 class 的区别
默认访问权限不同:
struct:默认权限为公共
class:默认权限为私有
class hhh1
{
int k;
};
struct hhh2
{
int k;
};
int main()
{
hhh1 a;
a.k = 1; //错误
struct hhh2 b;
b.k = 1;
}
4.1.3 成员属性设置为私有
优点:
1、将所有成员属性设置为私有,可以自己控制读写权限
class person
{
public:
void setname(string n_name)
{
name = n_name;
}
string getname()
{
return name;
}
private:
string name;
int age;
string hight;
};
int main()
{
person p;
p.setname("zhang");
cout<<p.getname()<<endl;
}
2、对于写权限,可以检测数据的有效性
#include<iostream>
using namespace std;
#include<string>
class person
{
public:
void setage(int n)
{
//设置年龄范围
if (n < 0 || n>150)
{
cout << "赋值失败" << endl;
return;
}
age = n;
}
int getage()
{
return age;
}
private:
string name;
int age;
string hight;
};
int main()
{
person p;
p.setage(140);
cout << p.getage() << endl;
}
案例一:设计立方体
1、求出立方体的体积和面积
2、分别用全局函数和成员函数判断两个立方体是否相等
#include<iostream>
using namespace std;
class cube
{
public:
void set(double cube_l, double cube_w, double cube_h)
{
if(cube_l<=0 || cube_w<=0 || cube_h<=0)
{
cout<<"赋值失败"<<endl;
return ;
}
l = cube_l;
w = cube_w;
h = cube_h;
}
double get_l()
{
return l;
}
double get_w()
{
return w;
}
double get_h()
{
return h;
}
double get_s()
{
return h*l*2+h*w*2+w*l*2;
}
double get_v()
{
return h*l*w;
}
bool isSame(cube &c)
{
if(l == c.get_l() && w == c.get_w() && h == c.get_h())
return true;
else return false;
}
private:
double l; //长
double w; //宽
double h; //高
};
bool isSame(cube& c1, cube& c2)
{
if(c1.get_l() == c2.get_l() && c1.get_w() == c2.get_w() && c1.get_h() == c2.get_h())
return true;
else return false;
}
int main()
{
cube cube_1;
double l, w, h;
cin>>l>>w>>h;
cube_1.set(l, w, h);
cout<<"s = "<<cube_1.get_s()<<endl;
cout<<"v = "<<cube_1.get_v()<<endl;
cube cube_2;
cin>>l>>w>>h;
cout<<"isSame = "<<isSame(cube_1, cube_2)<<endl;
cout<<"isSame_cube = "<<cube_1.isSame(cube_2)<<endl;
}
案例二:点和圆的关系
#include<iostream>
#include<math.h>
using namespace std;
class Point
{
public:
void setPoint(int x, int y)
{
point_x = x;
point_y = y;
}
int get_x()
{
return point_x;
}
int get_y()
{
return point_y;
}
private:
int point_x, point_y;
};
class circle
{
public:
void setCenter(int r, Point center)
{
circle_r = r;
circle_center = center;
}
int get_r()
{
return circle_r;
}
Point get_center()
{
return circle_center;
}
private:
int circle_r;
Point circle_center; //类可以作为成员变量
};
void isIncircle(circle &c, Point &p)
{
int a, b;
a = pow(c.get_center().get_x() - p.get_x(), 2);
b = pow(c.get_center().get_y() - p.get_y(), 2);
if(sqrt(a+b) == c.get_r())
cout<<"在圆上 1"<<endl;
else if(sqrt(a+b) < c.get_r())
cout<<"在圆内 2"<<endl;
else cout<<"在圆外 3"<<endl;
}
int main()
{
circle cir;
Point cir_center;
int x, y, r;
cin>>x>>y>>r;
cir_center.setPoint(x, y);
cir.setCenter(r, cir_center);
Point dot;
cin>>x>>y;
dot.setPoint(x, y);
isIncircle(cir, dot);
}
分文件编写
Circle.h/头文件
写定义,如:函数定义 void f ( ) ;
#pragma once //防止头文件重复包含
#include<iostream>
using namespace std;
class Point
{
public:
void setPoint(int x, int y);
int get_x();
int get_y();
private:
int point_x, point_y;
};
class circle
{
public:
void setCenter(int r, Point center);
int get_r();
Point get_center();
private:
int circle_r;
Point circle_center;
};
void isIncircle(circle &c, Point &p);
Circle.cpp/源文件
写实现,如:函数实现 void f ( ) { }
#include "Circle.h"
#include<iostream>
#include<math.h>
//如果不加 Point::该函数为全局函数,但该函数为成员函数,所有需要写 Point::作用域
void Point::setPoint(int x, int y)
{
point_x = x;
point_y = y;
}
int Point::get_x()
{
return point_x;
}
int Point::get_y()
{
return point_y;
}
void circle::setCenter(int r, Point center)
{
circle_r = r;
circle_center = center;
}
int circle::get_r()
{
return circle_r;
}
Point circle::get_center()
{
return circle_center;
}
void isIncircle(circle &c, Point &p)
{
int a, b;
a = pow(c.get_center().get_x() - p.get_x(), 2);
b = pow(c.get_center().get_y() - p.get_y(), 2);
if(sqrt(a+b) == c.get_r())
cout<<"在圆上 1"<<endl;
else if(sqrt(a+b) < c.get_r())
cout<<"在圆内 2"<<endl;
else cout<<"在圆外 3"<<endl;
}
main.cpp
#include<iostream>
#include "Circle.cpp"
#include "Circle.h"
using namespace std;
int main()
{
circle cir;
Point cir_center;
int x, y, r;
cin>>x>>y>>r;
cir_center.setPoint(x, y);
cir.setCenter(r, cir_center);
Point dot;
cin>>x>>y;
dot.setPoint(x, y);
isIncircle(cir, dot);
}
4.2 对象的初始化和清理
4.2.1 构造函数和析构函数
两个函数会被编译器自动调用,完成对象初始化和清理工作。如果我们不提供这两个函数,编译器会提供,编译器提供的构造函数和析构函数是空实现
-
构造函数:在创建对象时为对象的成员属性赋值,构造函数由编译器自动调用
-
析构函数:在对象销毁前系统自动调用,执行一些清理工作
构造函数:类名(){ }
1、没有返回值也不用写 void
2、函数名称与类名相同
3、构造函数可以有参数,因此可以发生重载
4、创建对象时自动调用构造,而且只会调用一次
析构函数:~类名(){ }
1、没有返回值也不用写 void
2、函数名称与类名相同,在名称前加上~
3、构造函数不可以有参数,因此不可以发生重载
4、在对象销毁前自动调用析构,而且只会调用一次
#include<iostream>
using namespace std;
class Person
{
public:
//构造函数
Person()
{
cout<<"Person 构造函数调用"<<endl;
}
//析构函数
~Person()
{
cout<<"Person 析构函数调用"<<endl;
}
};
void test()
{
Person p; //在栈上,test 执行完之后,释放对象
}
int main()
{
Person p; //执行后,只会执行构造而不执行析构,main 结束后执行析构
test();
return 0;
}
4.2.2 构造函数的分类及调用
分类:
按参数:有参构造和无参构造
按类型:普通构造和拷贝构造
调用:
括号法、显示法、隐式转换法
class Person
{
public:
//无参构造(默认构造),普通构造
Person(){}
//有参构造,普通构造
Person(int a)
{
age = a;
}
//拷贝构造函数
Person(const Person& p)
{
//将 p 对象所有的属性,拷贝到我身上
age = p.age;
}
~Person(){}
int a;
};
void test()
{
Person p1;
//括号法
Person p2(10);
Person p3(p2);
//注意:如果是调用默认构造,则不应该加()否则编译器会认为是一个函数声明
Person p1(); //错误
//显示法
Person p2 = Person(10);
Person p3 = Person(Person p2);
//注意:等号右侧为匿名对象
Person p(10); //特点:当前行执行结束后,系统会立即回收匿名对象
//注意:不要利用拷贝构造函数初始化匿名对象
Person(p3); //错误,编译器认为 Person(p3) == Person p3,相当于重定义
//隐式转换法
Person p2 = 10; //相当与 Person p4 = Person(10);
Person p3 = p2;
}
4.2.3 拷贝构造函数调用时机
- 使用一个已经创建完毕的对象来初始化一个新对象
void test1()
{
Person p1(20);
Person p2(p1);
}
- 值传递的方式给函数参数传值
void fun(Person k)//相当于 Person k = p;此时会执行拷贝构造函数.但仅仅是值传递,若 k 值改变 p 不会改变
{
k.age = 10;
}
void test2()
{
Person p;
work(p);
}
- 以值方式返回局部对象
Person work2()
{
Person p1;
cout<<(int*)&p1<<endl;
return p1;
}
void test 3()
{
Person p = work2();
cout<<(int*)&p<<endl;
}
// 两个地址打印出来的地址不同,说明不是同一个p
4.2.4构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
1、默认构造函数(无参,函数体为空)
2、默认析构函数(无参,函数体为空)
3、默认拷贝构造函数,对属性进行值拷贝
-
如果用户定义有参构造函数,c++不在提供默认无参构造,但会提供默认拷贝构造
-
如果用户定义拷贝构造函数,c++不会再提供其他构造函数
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
cout<<"默认构造函数"<<endl;
}
// Person(const Person &p)
// {
// cout<<"拷贝构造函数"<<endl;
// age = p.age;
// }
~Person()
{
cout<<"析构函数"<<endl;
}
int age;
};
int main()
{
Person p;
p.age = 18;
Person p2(p);
cout<<p2.age<<endl;
}
4.2.5深拷贝与浅拷贝🌟
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age_, int height_)
{
cout << "有参构造函数" << endl;
age = age_;
height = new int(height_);
}
~Person()
{
//将堆区开辟的数据做释放操作
if(height != NULL)
{
delete height;
height = NULL;
}
cout << "析构函数" << endl;
}
int age;
int *height;
};
void test()
{
Person p1(18, 180);
cout << "p1的年龄: " << p1.age << " p1的身高: " << p1.height << endl;
cout << "p1的年龄: " << p1.age << " p1的身高: " << *p1.height << endl;
Person p2(p1);
cout << "p2的年龄: " << p2.age << " p2的身高: " << p2.height << endl;
cout << "p2的年龄: " << p2.age << " p2的身高: " << *p2.height << endl;
}
int main()
{
test();
}
此时编译器会报错,原因: Person p2(p1),如果利用编译器提供的拷贝构造函数,会做浅拷贝操作,浅拷贝只是简单的值拷贝操作,此时height是一个指针,所以p2的height就被赋值为与p1相同的一块内存地址,在函数结束时,p2会优先进行析构函数释放空间,在进行p1的析构函数,带来的问题就是堆区的内存重复释放。此时浅拷贝的问题需要利用深拷贝来解决
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age_, int height_)
{
cout << "有参构造函数" << endl;
age = age_;
height = new int(height_);
}
Person(const Person &p)
{
cout << "拷贝构造函数" << endl;
age = p.age;
// 深拷贝解决浅拷贝的问题
height = new int(*p.height);
}
~Person()
{
// 将堆区开辟的数据做释放操作
if (height != NULL)
{
delete height;
height = NULL;
}
cout << "析构函数" << endl;
}
int age;
int *height;
};
void test()
{
Person p1(18, 180);
cout << "p1的年龄: " << p1.age << " p1的身高: " << p1.height << endl;
cout << "p1的年龄: " << p1.age << " p1的身高: " << *p1.height << endl;
Person p2(p1);
cout << "p2的年龄: " << p2.age << " p2的身高: " << p2.height << endl;
cout << "p2的年龄: " << p2.age << " p2的身高: " << *p2.height << endl;
}
int main()
{
test();
}
**总结:**如果属性有在堆区开辟的,一定要自己提供拷贝构造函数
4.2.6初始化列表
初始化属性,效率更高
#include <iostream>
using namespace std;
class Person
{
public:
// 第一种
Person() : a(10), b(20), c(30)
{
}
// 第二种
Person(int a_, int b_, int c_) : a(a_), b(b_), c(c_)
{
}
int a;
int b;
int c;
};
int main()
{
Person p1;
cout << p1.a << " " << p1.b << " " << p1.c << endl;
Person p2(10, 20, 30);
cout << p2.a << " " << p2.b << " " << p2.c << endl;
}
4.2.7静态成员
1、静态成员变量
-
所以对象共享同一份数据
-
编译阶段就开始分配内存
-
类内声明,类外初始化操作
class Person
{
public:
static int a;
};
int Person::a = 100; //必须在类外初始化,可以不用赋值,默认为0
void test()
{
Person p;
cout << p.a << endl;
}
两种访问方式:通过对象进行访问和通过类名进行访问
class Person
{
public:
static int a;
};
int Person::a;
void test()
{
Person p;
// 通过对象进行访问
cout << p.a << endl;
// 通过类名进行访问
cout << Person::a << endl;
}
**注意:**静态成员变量也是有访问权限的,私有权限在类外访问不到
class Person
{
private:
static int b;
};
int Person::b;
void test()
{
Person p;
cout << p.b << endl;
cout << Person::b << endl;
//两种方式都是错误的
}
2、静态成员函数
-
所有对象共享同一个函数
-
静态成员函数只能访问静态成员变量
class Person
{
public:
static void fun()
{
cout << "fun" << endl;
}
};
void test()
{
Person p;
// 通过对象访问
p.fun();
// 通过类名访问
Person::fun();
}
静态成员函数只能访问静态成员变量
class Person
{
public:
static void fun()
{
a = 100;
b = 100; // 错误
//定义p1、p2、p3……
//如果在静态函数中调用非静态成员变量,编译器不知道是那个对象的非静态成员变量
cout << "fun" << endl;
}
static int a;
int b;
};
int Person::a;
void test()
{
Person p;
// 通过对象访问
p.fun();
// 通过类名访问
Person::fun();
}
**注意:**静态成员函数也是有访问权限的
class Person
{
private:
static void func(){}
};
void test()
{
Person::func(); //报错,类外访问不到
}
4.3 c++对象模型和this指针
4.3.1 成员变量和成员函数分开存储
在c++中,类的成员变量和成员函数分开存储,空对象的内存空间为1
class Person
{
};
void test()
{
Person p;
// 空对象占用内存空间为:1
//c++编译器会给每个空对象分配一个字节空间,为了区别空对象占用内存的位置
//每个空对象有一个独一无二的内存地址
cout << sizeof(p) << endl; // 1
}
只有非静态变量才属于类的对象上
class Person
{
int a; // 非静态成员变量 属于类的对象上
static int b; // 静态成员函数 不属于类的对象上
void func() {} // 非静态成员变量 不属于类的对象上
static void func2() {} // 静态成员函数 不属于类的对象上
};
int Person::b = 10;
void test()
{
Person p;
cout << sizeof(p) << endl; // 4
}
4.3.2 this指针
-
每一个非静态成员函数只会诞生一份函数实例,那么多个同类型的对象会共用一块代码,c++通过提供特殊的对象指针解决上述问题,this指针。this指针指向被调用的成员函数所属的对象
-
this指针是隐含在每一个非静态成员函数内的一种指针,不需要定义,直接使用
用途:
- 当形参和成员变量同名时,可用this指针来区分
class Person
{
public:
Person(int age)
{
this->age = age;
}
int age;
};
void test()
{
Person p1(20);
cout << p1.age; // 20
}
- 在类的非静态成员函数中返回对象本身,可使用return *this
class Person
{
public:
Person(int age)
{
this->age = age;
}
Person &sum(Person &p)
{
this->age += p.age;
// this指向p2的指针,则*this指向的就是p2这个对象的本体
return *this;
}
int age;
};
void test()
{
Person p1(20);
Person p2(10);
// 链式编程思想,如:cout << p2.age << endl;
p2.sum(p1).sum(p1).sum(p2);
// 相当于返回p2之后,进行p2.sum(p1),再返回p2
cout << p1.age; // 20
cout << p2.age; // 100
}
**思考:**Person &sum(Person &p),返回引用,相当于返回p2的本体,如果不加引用:Person sum(Person &p),则返回的是值,此时输出的p2.age为多少? p2.age = 30,因为返回的是值,是本体利用拷贝构造函数创建的新的数据对象,返回的是p2',此时p2.sum(p1)=p2',然后进行p2'.sum(p1),调用结束后返回p2'',每次调用的Person对象都不是同一个,如果用值的方式返回,会创建一个新对象,但是用引用返回,会返回本体
4.3.3空指针访问成员函数
c++中空指针也可以调用成员函数,但需要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
class Person
{
public:
void showClassName()
{
cout << "Person" << endl;
}
void showPersonAge()
{
// 报错原因:传入指针为空
cout << age << endl;
}
int age;
};
void test()
{
Person *p1 = NULL;
p1->showClassName();
p1->showPersonAge(); // 错误
}
**修改:**加一个判定语句,提高代码健壮性
class Person
{
public:
void showClassName()
{
cout << "Person" << endl;
}
void showPersonAge()
{
// 提高代码健壮性
if (this == NULL)
{
return;
}
cout << age << endl;
}
int age;
};
void test()
{
Person *p1 = NULL;
p1->showClassName();
p1->showPersonAge();
}
4.3.4const修饰成员函数
常函数:
-
成员函数后加const称常函数
-
常函数内不可以修改成员属性
-
成员属性声明时加关键字mutable后,在常函数中依然可以修改
class Person
{
public:
// this指针的本质是指针常量,指针常量的特点:指针指向是不可以修改
// Person *const this;此时指针指向的值可以修改,指针指向不可以修改
/*在函数后加const,相当于const Person *const this;
此时指针指向的值和指针指向都不可以修改*/
//常函数
void showPerson() const
{
// a = 100; //报错,不允许修改
// 因为在函数体内部都有一个隐藏的this指针,a = 100 == this->a = 100
// this = NULL; //报错,因为this指针此时指向p,是不允许修改的
/* 但是this指针的值是可以修改的,如果不添加const,
"this->a = 100;"就是正确的*/
b = 100;
}
int a;
mutable int b; //特殊变量,在常函数中也可以修改值
};
void test()
{
Person p;
p.showPerson(); // 调用之后this指针便指向了p,所以不允许this指向空
}
常对象:
-
声明对象前加const称该对象为常对象
-
常对象只能调用常函数
class Person
{
public:
Person()
{
cout << "构造函数" << endl;
}
void showPerson() const {}
void fun()
{
a = 100; // 正确
}
int a;
mutable int b;
};
void test()
{
const Person p; // 常对象
// 特点:不允许修改指针指向的值
// p.a = 100; // 报错
p.b = 100; // 正确,在常对象下也可以修改
// 常对象只能调用常函数
p.showPerson(); // 正确
// p.fun(); //错误
// 常对象不可以调用普通成员函数,因为普通成员函数可以修改属性
}
4.4友元friend
让一个函数或者类访问另一个类中的私有成员,相当于只有你的好朋友才可以进你的房间
4.4.1 全局函数做友元
class Building
{
//添加友元函数
friend void test(Building &build);
public:
void Setting()
{
SittingRoom = "客厅";
BedRoom = "卧室";
}
public:
string SittingRoom;
private:
string BedRoom;
};
void test(Building &build)
{
cout << build.SittingRoom << endl;
cout << build.BedRoom << endl;
}
int main()
{
Building build;
build.Setting();
test(build);
return 0;
}
4.4.2 类做友元
class Building;
class friend_test
{
public:
friend_test();
~friend_test()
{
delete building;
}
void visit();
Building *building;
};
class Building
{
// 声明
friend class friend_test;
public:
Building();
public:
string sittingRoom;
private:
string bedRoom;
};
// 类外写成员函数
Building::Building()
{
sittingRoom = "客厅";
bedRoom = "卧室";
}
//若是在friend_test类的构造函数中分配地址
//此时building类在之后,编译器并不知道要分配多少内存,所以会报错
//而写在building类之后就不会报错
friend_test::friend_test()
{
building = new Building;
}
void friend_test::visit()
{
cout << building->sittingRoom << endl;
cout << building->bedRoom << endl;
}
int main()
{
friend_test f;
f.visit();
}
4.4.3 成员函数做友元
class Building;
class friend_test
{
public:
friend_test();
~friend_test();
void visit();
private:
Building *building;
};
class Building
{
friend void friend_test::visit();
public:
Building()
{
settingRoom = "客厅";
bedRoom = "卧室";
}
public:
string settingRoom;
private:
string bedRoom;
};
friend_test::friend_test()
{
building = new Building;
}
friend_test::~friend_test()
{
delete building;
}
void friend_test::visit()
{
cout << building->settingRoom << endl;
cout << building->bedRoom << endl;
}
int main()
{
friend_test f;
f.visit();
return 0;
}
4.5 运算符重载
对已有的运算符重新定义,赋予另一种功能,以适应不对数据类型
4.5.1 加号运算符重载
只能写一个,要么成员函数重载要么全局函数重载
class Person
{
public:
// 通过成员函数重载+号
// Person operator+(Person &p)
// {
// Person temp;
// temp.a = this->a + p.a;
// temp.b = this->b + p.b;
// return temp;
// }
void print()
{
cout << a << endl
<< b << endl;
}
public:
int a;
int b;
};
// 通过全局函数重载
Person operator+(Person &p1, Person &p2)
{
Person temp;
temp.a = p1.a + p2.a;
temp.b = p1.b + p2.b;
return temp;
}
void test()
{
Person p1;
p1.a = 10;
p1.b = 10;
Person p2;
p2.a = 10;
p2.b = 10;
Person p3;
p3 = p1 + p2;
//成员函数调用的本质:Person p3 = p1.operator+(p2);
//全局函数调用的本质:Person p3 = operator(p1, p2);
p3.print();
}
int main()
{
test();
return 0;
}
运算符重载可以函数重载
Person operator+(Person &p1, Person &p2)
Person operator+(Person &p1, int c)
void test()
{
operator+(p1, 10)
}
4.5.2 左移运算符重载
class Person
{
public:
int a;
int b;
};
// 利用全局函数重载左移运算符
//1
// void operator<<(ostream &cout, Person &p)
// {
// cout << p.a << endl
// << p.b << endl;
// }
//2
ostream &operator<<(ostream &cout, Person &p)
{
cout << p.a << endl
<< p.b << endl;
return cout;
}
int main()
{
Person p;
p.a = 10;
p.b = 10;
cout << p;
cout << p << endl;
}
第一个没有返回值,不能在cout << p后面追加endl,第二个返回输出流对象,可以追加endl,链式输出 ostream &cout其中cout只是形参,是一个别名,可以是其他名字,如:
ostream &operator<<(ostream &out, Person &p)
{
out << p.a << endl
<< p.b << endl;
return out;
}
但是引用时不能更改名字cout<<p<<endl;
通过友元函数实现重载左移运算符函数输出私有成员
class Person
{
friend ostream &operator<<(ostream &out, Person &p);
public:
Person(int c_) : c(c_) {}
int a;
int b;
private:
int c;
};
ostream &operator<<(ostream &out, Person &p)
{
out << p.a << endl
<< p.b << endl
<< p.c << endl;
return out;
}
int main()
{
Person p(20);
p.a = 10;
p.b = 10;
cout << p << "hello" << endl;
}