当前位置: 首页 > news >正文

从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入门编程,学习编程不再难

相关文章:

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

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

Robot Operating System 2: Design, Architecture, and Uses In The Wild

Robot Operating System 2: Design, Architecture, and Uses In The Wild (机器人操作系统 2&#xff1a;设计、架构和实际应用) 摘要&#xff1a;随着机器人在广泛的商业用例中的部署&#xff0c;机器人革命的下一章正在顺利进行。即使在无数的应用程序和环境中&#xff0c;也…...

TinyEngine 服务端正式开源啦!!!

背景介绍 TinyEngine 低代码引擎介绍 随着企业对于低代码开发平台的需求日益增长&#xff0c;急需一个通用的解决方案来满足各种低代码平台的开发需求。正是在这种情况下&#xff0c;低代码引擎应运而生。它是一种通用的开发框架&#xff0c;通过对低代码平台系统常用的功能进…...

网页设计与制作web前端设计html+css+js成品。电脑网站制作代开发。vscodeDrea 【企业公司宣传网站(HTML静态网页项目实战)附源码】

网页设计与制作web前端设计htmlcssjs成品。电脑网站制作代开发。vscodeDrea 【企业公司宣传网站&#xff08;HTML静态网页项目实战&#xff09;附源码】 https://www.bilibili.com/video/BV1Hp4y1o7RY/?share_sourcecopy_web&vd_sourced43766e8ddfffd1f1a1165a3e72d7605...

Avalonia学习(二十)-登录界面演示

今天开始继续Avalonia练习。 本节&#xff1a;演示实现登录界面 在网上看见一个博客&#xff0c;展示Avalonia实现&#xff0c;仿照GGTalk&#xff0c;我实现了一下&#xff0c;感觉是可以的。将测试的数据代码效果写下来。主要是样式使用&#xff0c;图片加载方式。 只有前…...

Spring依赖注入的魔法:深入DI的实现原理【beans 五】

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 Spring依赖注入的魔法&#xff1a;深入DI的实现原理【beans 五】 前言DI的基本概念基本概念&#xff1a;为什么使用依赖注入&#xff1a; 构造器注入构造器注入的基本概念&#xff1a;示例&#xff1a…...

【学习笔记】1、数字逻辑概论

1.1 数字信号 数字信号&#xff0c;在时间和数值上均是离散的。数字信号的表达方式&#xff1a;二值数字逻辑和逻辑电平描述的数字波形。 &#xff08;1&#xff09; 数字波形的两种类型 数值信号又称为“二值信号”。数字波形又称为“二值位形图”。 什么是一拍 一定的时…...

设置代理IP地址对网络有什么影响?爬虫代理IP主要有哪些作用?

在互联网的广泛应用下&#xff0c;代理IP地址成为了一种常见的网络技术。代理IP地址可以改变用户的上网行为&#xff0c;进而影响网络访问的速度和安全性。本篇文章将探讨设置代理IP地址对网络的影响&#xff0c;以及爬虫代理IP的主要作用。 首先&#xff0c;让我们来了解一下代…...

聊聊jvm的mapped buffer的统计

序 本文主要研究一下jvm的mapped buffer的统计 示例 private void writeDirectBuffer() {// 分配一个256MB的直接缓冲区ByteBuffer buffer ByteBuffer.allocateDirect(256 * 1024 * 1024);// 填充数据Random random new Random();while (buffer.remaining() > 4) {buff…...

matrix-breakout-2-morpheus 靶场 练习思路

arp-scan -l 获取目标机器的IP nmap -sV -A IP 查看目标机器开放的端口 gobuster dir -u http://192.168.29.130 -x php,txt,jsp,asp -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt 爆破目标机器的文件目录,找到可以访问的文件路径 http://192.168…...

【Flutter 开发实战】Dart 基础篇:从了解背景开始

想要学会用 Flutter 开发 App&#xff0c;就不可避免的要学习另一门很有意思的编程语言 —— Dart。很多小伙伴可能在学习 Flutter 之前可能都没听说过这门编程语言&#xff0c;我也是一样&#xff0c;还以为 Dart 是为了 Flutter 而诞生的&#xff1b;然而&#xff0c;当我们去…...

西电期末1017.有序序列插值

一.题目 二.分析与思路 简单题。主要考察简单的排序&#xff0c;最后的插入数据同样不用具体实现&#xff0c;只需在输出时多输出一下即可&#xff0c;注意顺序&#xff01;&#xff01; 三.代码实现 #include<bits/stdc.h>//万能头 int main() {int n;scanf("%d…...

day10 用栈实现队列 用队列实现栈

题目1&#xff1a;232 用栈实现队列 题目链接&#xff1a;232 用栈实现队列 题意 用两个栈实现先入先出队列&#xff08;一个入栈&#xff0c;一个出栈&#xff09;&#xff0c;实现如下功能&#xff1a; 1&#xff09;push&#xff1a;将元素x推到队列末尾 2&#xff09;…...

解决跨域问题(SpringBoot)

“什么是跨域&#xff1f;” 跨域 &#xff08;Cross-Origin&#xff09; 是指在浏览器的同源策略&#xff08;Same-Origin Policy&#xff09;下&#xff0c;一个网页的源&#xff08;指协议、域名、端口号的组合&#xff09;与另一个网页的源不同。因此&#xff0c;不同源的…...

LeetCode——2487. 从链表中移除节点

通过万岁&#xff01;&#xff01;&#xff01; 题目&#xff1a;给你一个链表&#xff0c;然后让你从链表中移除一些节点&#xff0c;移除的规则就是我们选择的这个节点在原链表中往右不能有比这个节点大的值。思路&#xff1a;这个题我最开始以为是双指针&#xff0c;然后找…...

云原生和Kubernetes如何简化应用程序开发

在谈论当前技术时,“云计算”正变得非常普遍,作为开发人员,将会继续体验使用云计算应用程序的优势;在云计算中,另一个正在出现的术语是云原生。在进入实际话题之前,首先了解一下云原生到底是什么。 深入了解云原生应用 现在,世界各地的公司都了解云计算应用程序可以带来…...

点云从入门到精通技术详解100篇-基于深度学习的室内场景三维点云语义分割(续)

目录 CSegNet 语义分割模型构建 3.1 引言 3.2 偏移注意机制 3.3 网络主干 3.4 边缘卷积模块...

RabbitMQ消息可靠性保证机制3--消费端ACK机制

消费端ACK机制 ​ 在这之前已经完成了发送端的确认机制。可以保证数据成功的发送到RabbitMQ&#xff0c;以及持久化机制&#xff0c;然尔这依然无法完全保证整个过程的可靠性&#xff0c;因为如果消息被消费过程中业务处理失败了&#xff0c;但是消息却已经被标记为消费了&…...

Copilot在Pycharm的应用和示例

Copilot 是 Github 在 2021 年发布的 AI 代码助手工具&#xff0c;它可以根据你提供的上下文信息&#xff0c;自动生成代码建议&#xff0c;帮助提高代码编写效率和准确性。在 Pycharm 中使用 Copilot&#xff0c;可以进一步提升 Python 开发效率&#xff0c;本文将分享如何在 …...

搜维尔科技:【简报】第九届元宇宙数字人设计大赛,报名已经进入白热化阶段!

随着元宇宙时代的来临&#xff0c;数字人设计成为了创新前沿领域之一。为了提高大学生元宇宙虚拟人角色策划与美术设计的专业核心能力&#xff0c;我们特别举办了这场元宇宙数字人设计赛道&#xff0c;赛道主题为「AI人工智能科技」 &#xff0c;只要与「AI人工智能科技」相关的…...

性能检测自动化(含内存泄露检测)

一、平台侧实现方案 1、UI case重复执行N次:进入页面,sleep 5s,记录start_time,sleep 30s,记录end_time,性能采集工具全程采集性能数据 2、根据采集到的性能数据,按照N次卡点性能数据:去掉最大的10%、最小的10%,求取平均值,作为单次性能数据结果f(n)…...

iec104和iec61850

iec104和iec61850 IEC104 规约详细解读(一) 协议结构 IEC104 规约详细解读(二)交互流程以及协议解析 61850开发知识总结与分享【1】 Get the necesarry projects next to each other in the same directory; $ git clone https://github.com/robidev/iec61850_open_server.g…...

redis 面试问题 (更新中 ing)

目录 reids 是做什么的为什么那么快有哪些使用场景redis有哪些 数据结构redis 有哪些底层数据结构为什么设计 sds一个 字符串 存储多大容量 stream为什么设计 streamstream 消费者消息丢失stream 消息私信问题 持久化机制redis 持久化机制&#xff0c;优缺点&#xff0c;怎么用…...

力扣(leetcode)第389题找不同(Python)

389.找不同 题目链接&#xff1a;389.找不同 给定两个字符串 s 和 t &#xff0c;它们只包含小写字母。 字符串 t 由字符串 s 随机重排&#xff0c;然后在随机位置添加一个字母。 请找出在 t 中被添加的字母。 示例 1&#xff1a; 输入&#xff1a;s “abcd”, t “abcde…...

Linux_源码编译安装LAMP

1. 安装httpd服务 在配置 Apache 网站服务之前&#xff0c;需要正确安装好 httpd 服务器软件。httpd 服务器的安装可以选用 RPM 安装、源码编译安装这两种方式&#xff0c;前者相对比较简单、快速&#xff0c;但是在功能上存在一定的局限性。在实际的生产环境中&#xff0c;使…...

静态网页设计——清雅古筝网(HTML+CSS+JavaScript)

前言 声明&#xff1a;该文章只是做技术分享&#xff0c;若侵权请联系我删除。&#xff01;&#xff01; 感谢大佬的视频&#xff1a; https://www.bilibili.com/video/BV1T64y1K7Zn/?vd_source5f425e0074a7f92921f53ab87712357b 使用技术&#xff1a;HTMLCSSJS&#xff08;…...

实战Flink Java api消费kafka实时数据落盘HDFS

文章目录 1 需求分析2 实验过程2.1 启动服务程序2.2 启动kafka生产 3 Java API 开发3.1 依赖3.2 代码部分 4 实验验证STEP1STEP2STEP3 5 时间窗口 1 需求分析 在Java api中&#xff0c;使用flink本地模式&#xff0c;消费kafka主题&#xff0c;并直接将数据存入hdfs中。 flin…...

爬虫与反爬-localStorage指纹(某易某盾滑块指纹检测)(Hook案例)

概述&#xff1a;本文将用于了解爬虫中localStorage的检测原理以及讲述一个用于检测localStorage的反爬虫案例&#xff0c;最后对该参数进行Hook断点定位 目录&#xff1a; 一、LocalStorage 二、爬虫中localStorage的案例&#xff08;以某盾滑块为例&#xff09; 三、如何…...

聊一聊 webpack 和 vite 的开发服务代理的问题

webpack 和 vite webpackVite重新编辑的问题 changOrigin: true如何定义 /api ? webPack And Vite 都是两个比较好用的打包工具&#xff0c;尤其是 Vite, 几几年流行忘记了&#xff0c;特色就是服务启动极快&#xff0c;实现预加载&#xff0c;感觉 webPack 要比 Vite 要复杂一…...

【鸿蒙4.0】安装DevEcoStudio

1.下载安装包 HUAWEI DevEco Studio和SDK下载和升级 | HarmonyOS开发者华为鸿蒙DevEco Studio是面向全场景的一站式集成开发环境,&#xff0c;在鸿蒙官网下载或升级操作系统开发工具DevEco Studio最新版本&#xff0c;SDK配置和下载&#xff0c;2.1支持Mac、Windows操作系统。…...

网站页面设计制作费/百度seo优化公司

XHTML代码规范 1.所有的标记都必须要有一个相应的结束标记2.所有标签的元素和属性的名字都必须使用小写3.所有的XML标记都必须合理嵌套4.所有的属性必须用引号""括起来5.把所有<和&特殊符号用编码表示6.给所有属性赋一个值7.不要在注释内容中使“--” HEAD区示…...

广州市手机网站建设品牌/百度图片识别搜索

1.水平、垂直居中的几种方法。 2.块级、行内元素的区别&#xff1f; &#xff08;1&#xff09;占行&#xff0c;块级元素独占一行&#xff0c;行内元素跟其他的行内元素共用一行。 &#xff08;2&#xff09;width & height&#xff0c;块级可以设置width、height&#xf…...

河南省建筑业协会/专业seo站长工具全面查询网站

freeBSD的下载地址&#xff1a;http://mirrors.163.com/FreeBSD/ISO-IMAGES-amd64/8.3/FreeBSD-8.3-RELEASE-amd64-dvd1.iso用迅雷下载即可&#xff0c; 虚拟机的硬盘设定为20G&#xff0c;内存512MB&#xff0c;分区情况如下&#xff1a;/ 2048Mswap 1024M/usr 17401M下面详解…...

深圳建设资源交易服务中心网站/竞价托管的注意事项

商务智能工具已经成为执行重要任务的工具。因此&#xff0c;机构对商务智能工具的需求在日益增长。   事实上&#xff0c;企业用户需要商务智能成为他们工作领 域的一部分&#xff0c;与他们的公司门户网站和企业搜索等功能集成在一起&#xff0c;并且能够通过移动设备访问和…...

做网站销售会遇到哪些问题/百度投流

目录引言西安交通大学西北工业大学西安电子科技大学浙江工业大学南京理工大学引言 我妹是2022届考研&#xff0c;车辆工程专业&#xff0c;选择报考机械工程或车辆工程专硕&#xff0c;在她初次筛选的五所大学里(西安交大、西北工大、西安电子科技大、浙江工大、南京理工)&…...

常熟做网站/深圳网站设计专家乐云seo

Spring Boot介绍 对于熟悉Spring的读者读者来说&#xff0c;想必也听说过Spring Boot的大名&#xff0c;Spring Boot旨在简化Spring的开发&#xff0c;它涉及了Spring的方方面面&#xff0c;是一个令人惊叹的神奇发明。Spring Boot是由Pivotal团队提供的全新框架&#xff0c;其…...