从0到1入门C++编程——04 类和对象之封装、构造函数、析构函数、this指针、友元

文章目录

  • 一、封装
  • 二、项目文件拆分
  • 三、构造函数和析构函数
    • 1.构造函数的分类及调用
    • 2.拷贝函数调用时机
    • 3.构造函数调用规则
    • 4.深拷贝与浅拷贝
    • 5.初始化列表
    • 6.类对象作为类成员
    • 7.静态成员
  • 四、C++对象模型和this指针
    • 1.类的对象大小计算
    • 2.this指针
    • 3.空指针访问成员函数
    • 4.const修饰成员函数
  • 五、友元
    • 1.全局函数做友元
    • 2.类做友元
    • 3.成员函数做友元

一、封装

C++面向对象的三大特性为:封装、继承、多态。
C++认为万事万物皆为对象,对象都有其属性和行为。比如人作为对象,属性有姓名、年龄、身高、体重、性别等,行为有走、跑、跳等。
具有相同性质的对象,可以抽象称为类。
封装是C++面向对象的三大特性之一,封装的意义是将属性和行为作为一个整体表现对象或事务,同时将属性和行为加以权限控制。
类中的属性和行为统一称为成员。类中的属性称为成员属性或者成员变量,类中的行为称为成员函数或成员方法。
访问的权限有三种,公共权限(public)、保护权限(protected)和私有权限(private)。
公共权限类内可以访问,类外也可以访问;保护权限和私有权限类内可以访问,类外不可以访问。保护权限和私有权限的区别在继承时有所体现,子类可以访问父类保护权限下的属性和行为,但是子类不可以访问父类私有权限下的属性和行为。
类的默认权限是私有权限(private)。结构体的默认权限是公共。
下面代码就是一个创建类并实例化类的的例子。

#include <iostream>
using namespace std;
#define PI 3.14//圆类
class Circle
{//访问权限
public:   //公共权限//属性  一般就是变量int radius;//行为  一般用函数来实现double perimeter(){return 2*PI*radius;}double area(){return PI*radius*radius;}
};int main()
{//通过圆类创建一个具体的圆对象——实例化Circle c1,c2;//给圆对象的属性赋值c1.radius = 1;c2.radius = 10;cout<<"半径为"<<c1.radius<<"的圆的周长为:"<<c1.perimeter()<<endl;cout<<"半径为"<<c2.radius<<"的圆的面积为:"<<c2.area()<<endl;system("pause");return 0;
}

上面程序的运行结果如下图所示。
在这里插入图片描述
创建一个学生类,设置姓名、学号属性,显示学生信息的行为,姓名和学号可以由用户输入,示例如下。
在这里插入图片描述
在类中也可以通过行为为属性赋值。

#include <iostream>
#include <string>
using namespace std;class Student
{
public:string name;string num;void show(){cout<<"姓名:"<<name<<" 学号:"<<num<<endl;}void set_name(string s_name,string s_num){name = s_name;num = s_num;}
};int main()
{Student s;s.set_name("Jones","20240105");cout<<"学生信息"<<endl;s.show();system("pause");return 0;
}

在设计类的时候,可以把属性和行为放在不同的权限下加以控制。
如果在主函数中访问保护权限或私有权限下的属性,代码中会直接报错。
在这里插入图片描述
将类中成员的属性设置为私有,然后通过在类中写一些公共权限的行为来控制读写权限。

#include <iostream>
#include <string>
using namespace std;class Person
{
public:void setAttribute(string s,int a,int b){name = s;age = a;password = b;}string getName(){return name;}int getAge(){return age;}int getPassword(){return password;}private:string name;   //可读可写int age;int password;
};int main()
{Person p;p.setAttribute("Jones",20,123456);cout<<"姓名:"<<p.getName()<<endl;cout<<"年龄:"<<p.getAge()<<endl;cout<<"密码:"<<p.getPassword()<<endl;system("pause");return 0;
}

虽然上面代码中的属性不能再main函数中直接访问修改,但是可以通过调用类中的公有方法来修改或者读取其属性。
立方体的案例
下面代码中设计了一个立方体类,然后通过成员函数求立方体的面积和体积,并分别利用全局函数和成员函数判断两个立方体是否相同。

#include <iostream>
#include <string>
using namespace std;class Cube
{
public:void set_attr(double a,double b,double c){c_l = a;c_w = b;c_h = c;}double get_length(){return c_l;}double get_wide(){return c_w;}double get_height(){return c_h;}double cube_area(){return 2*(c_l*c_w+c_l*c_h+c_w*c_h);}double cube_volume(){return c_l*c_w*c_h;}//利用全局函数判断两个立方体是否相同bool isSame(Cube &a)  //成员函数可以与全局函数同名{if(a.get_length()== c_l && a.get_wide()==c_w && a.get_height()==c_h)return true;return false;}
private:double c_l;double c_w;double c_h;
};//利用全局函数判断两个立方体是否相同
bool isSame(Cube &a,Cube &b)
{if(a.get_length()==b.get_length() && a.get_wide()==b.get_wide() && a.get_height()==b.get_height())return true;return false;
}int main()
{Cube c1,c2;c1.set_attr(10,10,10);cout<<"立方体的面积:"<<c1.cube_area()<<endl;cout<<"立方体的体积:"<<c1.cube_volume()<<endl;c2.set_attr(10,10,10);//利用全局函数判断if(isSame(c1,c2))cout<<"1.两个立方体相同!"<<endl;elsecout<<"1.两个立方体不相同!"<<endl;//利用成员方法判断if(c1.isSame(c2))cout<<"2.两个立方体相同!"<<endl;elsecout<<"2.两个立方体不相同!"<<endl;system("pause");return 0;
}

程序运行结果如下图所示。
在这里插入图片描述
判断点和圆关系的案例 ——在类中可以让另一个类作为本类的成员。
点和圆的关系有三种:点在圆外、点在圆上、点在圆内。
下面代码完成的就是点和圆位置关系的判断。

#include <iostream>
#include <string>
using namespace std;class Point
{
public://设置坐标void set_xy(int a,int b){x = a;y = b;}int get_x(){return x;}int get_y(){return y;}
private:int x;int y;
};class Circle
{
public://设置半径void set_r(int a){radius = a;}int get_r(){return radius;}//设置圆心void set_center(Point p){center = p;}Point get_center(){return center;}
private:int radius;  //半径Point center;  //圆心
};//判断点与圆的关系
void point_circle(Circle &c,Point &p)
{int x,y,r;r = c.get_r();x = p.get_x()-c.get_center().get_x();y = p.get_y()-c.get_center().get_y();if(x*x+y*y > r*r)cout<<"点在圆外!"<<endl;else if(x*x+y*y < r*r)cout<<"点在圆内!"<<endl;elsecout<<"点在圆上!"<<endl;
}int main()
{Point p1,p2;p1.set_xy(0,0);p2.set_xy(1,1);Circle c;c.set_center(p1);c.set_r(1);point_circle(c,p2);system("pause");return 0;
}

二、项目文件拆分

项目文件拆分的作用可以使得功能更加清楚。
将上面判断点和圆关系的案例代码中的类都独立的写到头文件和各自的C++文件中实现。
项目中包含圆类和点类的头文件,源文件中有圆类方法的实现、点类方法的实现文件以及包含主函数的文件。
在这里插入图片描述
point.h文件中只做变量和函数的声明,不做函数实现。

#pragma once  //防止头文件重复包含
#include <iostream>
using namespace std;//在头文件只做变量和函数的声明,不做函数实现
class Point
{
public://设置坐标void set_xy(int a,int b);int get_x();int get_y();
private:int x;int y;
};

点类中成员函数的实现放在point.cpp文件中,不过要在每个函数前面加上作用域。

#include "point.h"   //包含对应的头文件//在C++文件做成员函数实现
void Point::set_xy(int a,int b)  //需要告知成员函数的作用域 Point::
{x = a;y = b;
}
int Point::get_x()
{return x;
}
int Point::get_y()
{return y;
}

同样地,circle.h文件中只做变量和函数的声明,不做函数实现,且Circle类用到了Point类,需要将点类的头文件包含进来。

#pragma once  //防止头文件重复包含
#include <iostream>
#include "point.h"
using namespace std;//在头文件只做变量和函数的声明,不做函数实现
class Circle
{
public://设置半径void set_r(int a);int get_r();//设置圆心void set_center(Point p);Point get_center();
private:int radius;  //半径Point center;  //圆心
};

圆类中成员函数的实现放在circle.cpp文件中,同样要在每个函数前面加上作用域。

#include "circle.h"   //包含对应的头文件//在C++文件做成员函数实现
//设置半径
void Circle::set_r(int a)  //需要告知成员函数的作用域 Circle::
{radius = a;
}
int Circle::get_r()
{return radius;
}
//设置圆心
void Circle::set_center(Point p)
{center = p;
}
Point Circle::get_center()
{return center;
}

main.cpp文件中需要包含上面的两个头文件,然后在主函数中进行函数调用即可完成相应的功能。

#include <iostream>
#include <string>
#include "point.h"
#include "circle.h"
using namespace std;//判断点与圆的关系
void point_circle(Circle &c,Point &p)
{int x,y,r;r = c.get_r();x = p.get_x()-c.get_center().get_x();y = p.get_y()-c.get_center().get_y();if(x*x+y*y > r*r)cout<<"点在圆外!"<<endl;else if(x*x+y*y < r*r)cout<<"点在圆内!"<<endl;elsecout<<"点在圆上!"<<endl;
}int main()
{Point p1,p2;p1.set_xy(0,0);p2.set_xy(1,1);Circle c;c.set_center(p1);c.set_r(1);point_circle(c,p2);system("pause");return 0;
}

三、构造函数和析构函数

C++中的每个对象都会有初始设置以及对象销毁前的数据清理。
对象的初始化和清理是两个非常重要的安全问题。一个对象或者变量没有初始状态,对其使用后果是未知;使用完一个对象或者变量,没有及时清理的话,也会造成一定的安全问题。
C++利用了构造函数和析构函数分别解决初始化和清理问题,这两个函数将会被编译器自动调用,完成对象的初始化和清理工作。 对象的初始化和清理工作是编译器强制做的事情,如果我们不提供构造函数和析构函数,编译器会执行编译器提供的构造函数和析构函数,不过都是空实现。
构造函数:创建对象时为对象的成员属性赋值,由编译器自动调用,无须手动调用。
析构函数:对象销毁前系统自动调用,执行清理工作。
构造函数语法:类名(){}
构造函数没有返回值,也不用写void;函数名称与类名相同;构造函数可以有参数,可以发生函数重载;程序在调用对象的时候会自动调用构造函数,而且只调用一次,无须手动调用。
析构函数语法:~类名(){}
析构函数没有返回值,也不用写void;函数名称与类名相同,不过要在类名前面加上~符号;析构函数不可以有参数,因此不可以发生函数重载;程序在对象销毁前会自动调用析构函数,而且只调用一次,无须手动调用。

#include <iostream>
#include <string>
using namespace std;class Person
{
public://构造函数Person(){cout<<"Person构造函数调用!"<<endl;}//析构函数~Person(){cout<<"Person析构函数调用!"<<endl;}
};void fun()
{Person p;  //存放在栈区,函数执行完以后会释放空间,因此会调用析构函数
}int main()
{fun();system("pause");return 0;
}

上面代码运行后的结果如下图所示,虽然只是在函数中实例化了一个Person类对象,但是构造函数和析构函数都执行了。
在这里插入图片描述
如果不是通过函数实例化对象,而是将函数中的代码直接放在主函数中,析构函数输出的内容需要按下任意键后才会执行。

1.构造函数的分类及调用

构造函数按照参数的有无分为有参构造和无参构造,无参构造也称默认构造;按类型分为普通构造和拷贝构造。
构造函数的调用方式:括号法、显示法、隐式转换法。

#include <iostream>
#include <string>
using namespace std;class Person
{
public://构造函数Person(){cout<<"Person无参构造函数调用!"<<endl;}Person(int a){age = a;cout<<"Person有参构造函数调用!"<<endl;}Person(const Person &p)  //不能改变原有类的属性,要用const{age = p.age;  //将参数中的所有属性加以拷贝cout<<"Person拷贝构造函数调用!"<<endl;}//析构函数~Person(){cout<<"Person析构函数调用!"<<endl;}
public:int age;
};void fun()
{//1.括号法调用构造函数Person p1;   //默认(无参)构造函数的调用 //Person p1();   不能实现无参构造函数的调用,编译器会认为这是函数声明Person p2(20);   //有参构造函数的调用Person p3(p2);   //拷贝构造函数的调用cout<<"p2年龄:"<<p2.age<<endl;cout<<"p3年龄:"<<p3.age<<endl;//2.显式法调用构造函数Person p4 = Person(20);   //Person(20)单独成行叫做匿名对象,执行该行后系统会回收掉匿名对象Person p5 = Person(p4);   //匿名对象前有等号,其就不是匿名对象了//Person(p4);   //不要利用拷贝函数初始化匿名对象,这行等价于 Person p2; 其已经定义过了//3.隐式法调用构造函数Person p6 = 20;   //编译器会将其转换为  Person p6 = Person(20);Person p7 = p6;
}int main()
{fun();system("pause");return 0;
}

下面的构造函数调用方式不能实现无参构造函数的调用,编译器会认为这是函数声明。

Person p1();   

Person(20)单独在一行叫做匿名对象,执行完该行后系统会立即回收掉匿名对象。

Person(20); 

对于匿名对象,函数执行完匿名对象这行后,立即调用了析构函数回收清理,然后才执行后面的打印行。
在这里插入图片描述
不要利用拷贝函数初始化匿名对象,其效果相当于自动去掉括号,会发生重定义错误。

Person(p1);

2.拷贝函数调用时机

拷贝函数调用时机:使用一个已经创建完毕的对象来初始化一个新对象;值传递的方式给函数传值;值方式返回局部对象。
1.使用一个已经创建完毕的对象来初始化一个新对象。

Person p1(20);  //有参构造函数调用
Person p2(p1);  //拷贝构造函数调用
//拷贝完成后对一个对象属性的修改不会影响到另一个对象

2.值传递的方式给函数传值这种情况下,函数形参这里会进行拷贝函数的调用。

void fun(Person p)
{...
}int main()
{Person p;fun(p);...
}

3.值方式返回局部对象

Person fun()
{Person p;return p;  //返回的局部变量是一份拷贝
}int main()
{Person p = fun(p);...
}

3.构造函数调用规则

默认情况下,C++编译器至少给一个类添加3个函数:默认构造函数(空实现)、默认析构函数(空实现)、默认拷贝函数(值拷贝)。
构造函数调用规则:如果用户定义了有参构造函数,C++不再提供无参构造函数,但会提供默认拷贝函数;如果用户定义了拷贝构造函数,C++不会再提供其他的构造函数。
按照无参构造函数、有参构造函数、拷贝构造函数由前及后的顺序,若定义了前边的构造函数,后面的构造函数编译器仍然提供,若定义了后面的构造函数,前面的构造函数编译器便不再提供,需要使用的话就要自己定义实现。
下面的代码中没有写拷贝构造函数,但是在函数体中调用了拷贝构造函数。

#include <iostream>
#include <string>
using namespace std;class Person
{
public://构造函数Person(){cout<<"Person无参构造函数调用!"<<endl;}Person(int a){age = a;cout<<"Person有参构造函数调用!"<<endl;}//析构函数~Person(){cout<<"Person析构函数调用!"<<endl;}
public:int age;
};void fun()
{Person p1(20);  //有参构造函数调用Person p2(p1);  //拷贝构造函数调用cout<<"p1的年龄:"<<p1.age<<endl;cout<<"p2的年龄:"<<p2.age<<endl;
}int main()
{fun();system("pause");return 0;
}

代码的执行结果如下图所示。
在这里插入图片描述

4.深拷贝与浅拷贝

浅拷贝就是简单的赋值拷贝操作。
深拷贝是在堆区重新申请空间进行拷贝的操作。
如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。
下面这段程序实现的就是浅拷贝,默认拷贝函数中的内容就是下面写的这样,将属性逐一复制。在析构函数中对构造函数中申请的堆区内存进行了释放,fun()函数中先后实例化了p1和p2两个对象,p2对象后入栈,所以先执行析构函数对堆区内存进行释放,这里不会出问题。当p1对象执行析构函数的时候,成员变量height的值是非空的,但是申请的堆区内存已经被p2释放掉了,这个时候程序就会出现问题。

#include <iostream>
#include <string>
using namespace std;class Person
{
public://构造函数Person(){cout<<"Person无参构造函数调用!"<<endl;}Person(int a,int b){age = a;height = new int(b);cout<<"Person有参构造函数调用!"<<endl;}//拷贝构造函数Person(const Person &p){//默认的拷贝构造函数将属性逐一复制age = p.age;//height = p.height;  //浅拷贝height = new int(*p.height);  //深拷贝cout<<"Person拷贝构造函数调用!"<<endl;}//析构函数~Person(){if(height != NULL){delete height;height = NULL;}cout<<"Person析构函数调用!"<<endl;}
public:int age;int *height;
};void fun()
{Person p1(20,175);  //有参构造函数调用cout<<"p1的年龄:"<<p1.age<<" p1的身高:"<<*p1.height<<endl;Person p2(p1);  //有参构造函数调用cout<<"p2的年龄:"<<p2.age<<" p2的身高:"<<*p2.height<<endl;
}int main()
{fun();system("pause");return 0;
}

深拷贝的实现也很简单,把拷贝构造函数中涉及堆区内存变量拷贝的,独自申请一块堆区内存来接收,这样一来,虽然拷贝后指针指向的位置不再与原来的地址相同,但是里面存放的值是一样的。
对于浅拷贝而言,不写拷贝构造函数也是可以的,采用默认的拷贝构造函数,但是对于深拷贝来说,拷贝构造函数中需要重新申请堆区内存存放值。

Person(const Person &p)
{//默认的拷贝构造函数将属性逐一复制age = p.age;//height = p.height;  //浅拷贝height = new int(*p.height);  //深拷贝cout<<"Person拷贝构造函数调用!"<<endl;
}

改为深拷贝后,程序的运行结果如下图所示。
在这里插入图片描述

5.初始化列表

初始化列表赋值的语法:构造函数(): 属性1(值1),属性2(值2), …,属性n(值n){}
也可以在构造函数中加入参数,并将参数值一一赋给属性值,这样在赋值时更加灵活,比如:构造函数(int a, int b): 属性1(a),属性2(b){}

#include <iostream>
#include <string>
using namespace std;class Person
{
public://传统方式赋值Person(int a,int b){x = a;y = b;}//初始化列表方式赋值Person():x(10),y(20){}public:int x;int y;
};void fun()
{Person p1;  //初始化列表方式赋值Person p2(11,22);  //传统方式赋值cout<<"1.a="<<p1.x<<" b="<<p1.y<<endl;cout<<"2.a="<<p2.x<<" b="<<p2.y<<endl;
}int main()
{fun();system("pause");return 0;
}

6.类对象作为类成员

C++类中的成员可以是另一个类的对象,称该成员为对象成员。
当其他类对象作为本类的成员时,构造的时候先构造类对象,然后再构造自身。
比如下面的例子,B类中有对象A作为成员,A为对象成员。构造时先执行A的构造函数,再执行B的构造函数。

class A
{...
}
class B
{A a;
}

构造时先构造类对象,再构造自身;析构的时候先析构自身,再析构类对象。

#include <iostream>
#include <string>
using namespace std;class Phone
{
public:Phone(string s){name = s;cout<<"Phone的构造函数!"<<endl;}~Phone(){cout<<"Phone的析构函数!"<<endl;}string name;
};class Person
{
public:Person(string s1,string s2):name(s1),brand(s2){cout<<"Person的构造函数!"<<endl;}~Person(){cout<<"Person的析构函数!"<<endl;}public:string name;Phone brand;
};void fun()
{Person p("Jones","iphone"); cout <<p.name<<"手机牌子是"<<p.brand.name<<endl;
}int main()
{fun();system("pause");return 0;
}

上面代码执行后的结果如下图所示。
在这里插入图片描述

7.静态成员

静态成员就是在成员变量或者成员函数前加关键字static。静态成员分为静态成员变量和静态成员函数。
静态成员变量特点:所有对象共享同一份数据,一个对象修改值后,其他对象访问到的就是修改后的新值;在编译阶段分配内存;静态成员变量在类内声明,类外初始化,初始化的时候要加作用域。
静态成员变量可以通过类名直接访问,其也有访问权限。

#include <iostream>
#include <string>
using namespace std;class Person
{
public:static int a;  //类内声明
};int Person::a = 10;  //类外初始化,要加作用域,否则就是全局变量void fun()
{Person p1; cout <<"1.a="<<p1.a<<endl;Person p2; p2.a = 20;cout <<"2.a="<<p1.a<<endl;  //所有对象共享一份数据cout <<"3.a="<<Person::a<<endl;  //通过类名直接访问静态变量
}int main()
{fun();system("pause");return 0;
}

上面代码运行后的结果如下图所示。
在这里插入图片描述
静态成员函数特点:所有对象共享同一个函数;静态成员函数只能访问静态成员变量,不可以访问非静态成员变量,非静态成员变量必须与特定的对象相对。
静态成员函数可以通过对象访问,也可以通过类名访问。

#include <iostream>
#include <string>
using namespace std;class Person
{
public:static int a;  //类内声明static void fun(){a = 100;cout <<"静态成员函数的调用!"<<endl;}
};int Person::a = 10;  //类外初始化,要加作用域,否则就是全局变量int main()
{Person p;cout <<"1.a="<<Person::a<<endl;p.fun();cout <<"2.a="<<Person::a<<endl;Person::fun();system("pause");return 0;
}

上面代码运行后的结果如下图所示。
在这里插入图片描述


四、C++对象模型和this指针

1.类的对象大小计算

在C++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上。
空对象占用的内存空间为1个字节大小。
C++编译器会为每个空对象分配一个字节的空间,这是为了区分不同对象占用内存的位置,比如声明两个空对象,不能让它们指向同一块内存空间,每个对象应该有一块独一无二的空间,即使它是空的。
在这里插入图片描述
只有非静态成员变量的大小才算在类的对象上,静态变量、静态函数、函数都不算字节空间。
在这里插入图片描述
对象也存在字节对齐。
在这里插入图片描述

2.this指针

C++提供特殊的对象指针——this指针,this指针指向被调用的成员函数所属的对象
this指针是隐含在每一个非静态成员函数内的一种指针,其不需要定义,直接使用。
this指针的用途:当形参和成员变量同名的时候,可以用this指针来进行区分,解决名称冲突;在类的非静态成员函数中返回对象本身,可以使用return *this。
this指针解决名称冲突的例子如下图所示。
在这里插入图片描述
在类的非静态成员函数中返回对象本身的例子如下。

#include <iostream>
#include <string>
using namespace std;class Person
{
public:Person(int age){this->age = age;}Person& Addage(Person &p)  //以引用的方式返回{this->age += p.age;return *this;  //返回对象本身}int age;
};int main()
{Person p1(10);Person p2(20);p2.Addage(p1).Addage(p1).Addage(p1);  //链式编程的思想cout <<"age="<<p1.age<<endl;cout <<"age="<<p2.age<<endl;system("pause");return 0;
}

因为成员函数返回的是对象本身,因此可以连续调用成员函数,这就是链式编程思想的体现。
上面代码的运行结果如下图所示。
在这里插入图片描述

3.空指针访问成员函数

空指针访问成员变量的时候就会报错,因为类中成员变量前默认有一个this指针,如果指针是空的,自然是访问不到,因此为了程序的健壮性,应当在访问之前先判断所传的指针是否为空再进行下一步操作。

#include <iostream>
#include <string>
using namespace std;class Person
{
public:void show_name(){cout <<"class name : Person"<<endl;}void show_age(){if(this == NULL)return;cout <<"age="<<age<<endl; //与下一行代码是一样的//cout <<"age="<<this->age<<endl;}int age;
};int main()
{Person *p = NULL;p->show_name();p->show_age();system("pause");return 0;
}

4.const修饰成员函数

成员函数后加const称为常函数,常函数内不可以修改成员属性。 成员属性声明时加关键字mutable后,在常函数中仍然可以修改。
声明对象前加const称为常对象,常对象只能调用常函数. 因为普通函数中有可能对成员属性作修改,如果调用相当于变向的修改了属性。成员属性声明时加关键字mutable后,通过常对象访问可以修改。

class Person
{
public://this指针的本质是指针常量,指针的指向不可以修改,但是值是可以修改的//this指针相当于Person * const thisvoid show_age1(){this->age = 10;   //可以修改}//此时this指针再被const修饰,相当于const Person * const this,指针的指向和值都不可以被修改void show_age2() const{this->age = 20;   //不可以修改m_age = 20;   //可以修改}int age;mutable int m_age;
};const Person p;
p.age = 10;   //不可以修改
p.m_age = 20;  //可以修改

五、友元

友元的目的是让一个函数或类访问另一个类中的私有成员(private)。
友元的关键字是friend
友元的实现:全局函数做友元;类做友元;成员函数做友元。

1.全局函数做友元

全局函数做友元的代码如下。

#include <iostream>
#include <string>
using namespace std;class Room
{//全局函数是友元,可以访问类中的私有成员friend void fun(Room &r); 
public:Room(){sitting_room = "sitting room";bedroom = "bedroom";}
public:string sitting_room;
private:string bedroom;
};//全局函数
void fun(Room &r)
{cout<<"Room name:"<<r.sitting_room<<endl;cout<<"Room name:"<<r.bedroom<<endl;  //全局函数以友元的方式才能访问类中私有成员
}int main()
{Room room;fun(room);system("pause");return 0;
}

2.类做友元

可以将类中的成员函数写在类外,不过要在其前面加上作用域,而且在类中要先声明该函数。
类做友元的代码如下。

#include <iostream>
#include <string>
using namespace std;class Room;
class Person
{
public:Person();void visit();
private:Room *room;
};class Room
{//以类做友元friend class Person;
public:Room();string sitting_room;
private:string bedroom;
};//类外写构造函数,需要说明类作用域
Room::Room()
{sitting_room = "sitting room";bedroom = "bedroom";
}Person::Person()
{//在堆上开辟内存room = new Room;
}void Person::visit()
{cout<<"Room name:"<<room->sitting_room<<endl;cout<<"Room name:"<<room->bedroom<<endl;
}int main()
{Person p;p.visit();system("pause");return 0;
}

3.成员函数做友元

成员函数做友元的代码如下。

#include <iostream>
#include <string>
using namespace std;class Room;
class Person
{
public:Person();void visit();
private:Room *room;
};class Room
{//以类中成员函数做友元friend void Person::visit();
public:Room();string sitting_room;
private:string bedroom;
};//类外写构造函数,需要说明类作用域
Room::Room()
{sitting_room = "sitting room";bedroom = "bedroom";
}Person::Person()
{//在堆上开辟内存room = new Room;
}void Person::visit()
{cout<<"Room name:"<<room->sitting_room<<endl;cout<<"Room name:"<<room->bedroom<<endl;
}int main()
{Person p;p.visit();system("pause");return 0;
}

上面三个程序的运行结果相同,如下图所示。
在这里插入图片描述


本文参考视频:
黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/232950.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#,入门教程(08)——基本数据类型及使用的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(07)——软件项目的源文件与目录结构https://blog.csdn.net/beijinghorn/article/details/124139947 数据类型用于指定数据体&#xff08;DataEntity&#xff0c;包括但不限于类或结构体的属性、变量、常量、函数返回值&#xff09;…

16、Kubernetes核心技术 - 节点选择器、亲和和反亲和

目录 一、概述 二、节点名称 - nodeName 二、节点选择器 - nodeSelector 三、节点亲和性和反亲和性 3.1、亲和性和反亲和性 3.2、节点硬亲和性 3.3、节点软亲和性 3.4、节点反亲和性 3.5、注意点 四、Pod亲和性和反亲和性 4.1、亲和性和反亲和性 4.2、Pod亲和性/反…

stable diffusion 进阶教程-controlnet详解(持续更新中)

说明 插件下载链接:https://pan.baidu.com/s/1-qmJzqcB72nTv_2QLmR-gA?pwd=8888 提取码: 8888 讨论Q群:830970289 个人微信:mindcarver 如果在按着教程尝试的过程中有错误或问题,可以上面询问讨论,或者评论区留言 如果教程有什么问题,请帮忙纠正,持续更新(部分控制插件…

Android开发编程从入门到精通,安卓技术从初级到高级全套教学

一、教程描述 本套教程基于JDK1.8版本&#xff0c;教学内容主要有&#xff0c;1、环境搭建&#xff0c;UI布局&#xff0c;基础UI组件&#xff0c;高级UI组件&#xff0c;通知&#xff0c;自定义组件&#xff0c;样式主题&#xff1b;2、四大组件&#xff0c;Intent&#xff0…

数据库的连接

连接数据库 我们使用WinR输入cmd打开运行窗口 输入:sqlplus并回车 输入用户名和密码,我用的是Scott,密码我自己设置的123456,Scott默认的密码是tiger,回车 这种情况表示登录成功 在连接Scott成功的情况下创建一些数据,在我的资源里面有个Oracle数据基础可以下载,直接复制粘…

详解Java中的原子操作

第1章&#xff1a;什么是原子操作 大家好&#xff0c;我是小黑&#xff0c;面试中一个经常被提起的话题就是“原子操作”。那么&#xff0c;到底什么是原子操作呢&#xff1f;在编程里&#xff0c;当咱们谈论“原子操作”时&#xff0c;其实是指那些在执行过程中不会被线程调度…

59.网游逆向分析与插件开发-游戏增加自动化助手接口-文字资源读取类的C++还原

内容来源于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;游戏菜单文字资源读取的逆向分析-CSDN博客 码云地址&#xff08;master分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/sro_-ex.git 码云版本号&#xff1a;55358fb135a0c821d8e8…

AI实景无人直播创业项目:开启自动直播新时代,一部手机即可实现增长

在当今社会&#xff0c;直播已经成为了人们日常生活中不可或缺的一部分。无论是商家推广产品、明星互动粉丝还是普通人分享生活&#xff0c;直播已经渗透到了各行各业。然而&#xff0c;传统直播方式存在着一些不足之处&#xff0c;如需现场主持人操作、高昂的费用等。近年来&a…

C++多态性——(5)运算符重载(第二节)

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 身先才能率人&#xff0c;律己才能服人…

Windows重装升级Win11系统后 恢复Mysql数据

背景 因为之前电脑硬盘出现问题&#xff0c;换了盘重装了系统&#xff0c;项目的数据库全部没了&#xff0c;还好之前的Mysql是安装在的D盘里&#xff0c;还有留存文件 解决办法 1.设置环境变量 我的路径是 D:\SoftWare\Application\mysql-5.7.35-winx64 此电脑右键属性 …

简单 Web Server 程序的设计与实现 (2024)

1.题目描述 Web 服务是 Internet 最方便与受用户欢迎的服务类型&#xff0c;它的影响力也远远超出了专业技术范畴&#xff0c; 已广泛应用于电子商务、远程教育、远程医疗与信息服务等领域&#xff0c;并且有继续扩大的趋势。目前很多 的 Internet 应用都是基于 Web 技术的&…

MySQL5.7 InnoDB 内存结构

官网地址&#xff1a;MySQL :: MySQL 5.7 Reference Manual :: 14.5 InnoDB In-Memory Structures 欢迎关注留言&#xff0c;我是收集整理小能手&#xff0c;工具翻译&#xff0c;仅供参考&#xff0c;笔芯笔芯. MySQL 5.7 参考手册 / ... / 缓冲池 14.5.1 缓冲池 缓冲池是…

MySQL数据库进阶-事务

事务 事务由单独单元的一个或多个SQL语句组成&#xff0c;在这 个单元中&#xff0c;每个MySQL语句是相互依赖的。而整个单独单 元作为一个不可分割的整体&#xff0c;如果单元中某条SQL语句一 旦执行失败或产生错误&#xff0c;整个单元将会回滚。所有受到影 响的数据将返回到…

利用MATLAB绘制折线图

介绍 Matlab画图线型、符号及颜色汇总&#xff1a; https://blog.csdn.net/qq_40969467/article/details/90758281 实例&#xff1a; x20:20:140;%x轴上的数据&#xff0c;第一个值代表数据开始&#xff0c;第二个值代表间隔&#xff0c;第三个值代表终止a[0.85, 2.2, 3.45,…

高校选课系统需求分析开发源码

高校学生选课报名系统包括学生、教师和管理员三方的功能需求&#xff0c;学生的需求是查询院系的课程、选课情况及个人信息修改&#xff1b;教师则需要查看和查询所有课程信息及自己的课程信息以及教师信息的修改&#xff1b;管理员则负责更为复杂的任务&#xff0c;包括对学生…

解决 POST http://x.x.x.x:8000/aaa/ net::ERR_CONNECTION_TIMED_OUT

记录一下我遇到的问题和解决办法 我的项目前后端分离&#xff0c;在前端用的vue访问后端时连接不上后端&#xff0c;一切操作都触发不了后端&#xff0c;数据也传不到后端去&#xff1b; 原因&#xff1a;url有问题&#xff0c;url地址写的不是本机&#xff0c;所以导致连接超…

FA2016AA (MHz范围晶体单元超小型低轮廓贴片) 汽车

随着科技的不断发展&#xff0c;智能汽车逐渐成为人们出行的首选。而其中&#xff0c;频率范围在19.2 MHz ~ 54 MHz的晶体单元超小型低轮廓贴片&#xff08;FA2016AA&#xff09;为汽车打造更智能、更舒适、更安全的出行体验。FA2016AA贴片的外形尺寸为2.0 1.6 0.5 mm&#x…

原生JS调用OpenAI GPT接口并实现ChatGPT逐字输出效果

效果&#xff1a; 猜你感兴趣&#xff1a;springbootvue实现ChatGPT逐字输出打字效果 附源码&#xff0c;也是小弟原创&#xff0c;感谢支持&#xff01; 没废话&#xff0c;上代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><me…

网络层协议及IP编址

0x00 前言 本节为网络层协议及IP编址内容 IP地址的范围&#xff1a;0.0.0.0-255.255.255.255 IP分为网络位以及主机位。子网划分就是向主机位借位。 网络层协议 IPICMP&#xff08;internet Control message protocol&#xff09;IPX IP协议的作用 为网络层的设备提供逻…

车载 Android之 核心服务 - CarPropertyService 的VehicleHAL

前言: 本文是车载Android之核心服务-CarPropertyService的第二篇&#xff0c;了解一下CarPropertyService的VehicleHAL, 第一篇在车载 Android之 核心服务 - CarPropertyService 解析-CSDN博客&#xff0c;有兴趣的 朋友可以去看下。 本节介绍 AndroidAutomotiveOS中对于 Veh…