c++面向对象

一、类和对象

类将具有共性的数据和方法封装在一起,加以权限区分,用户只能通过公共方法 访问私有数据。
类的权限分为:private(私有)、protected(保护)、public(公有)3种权限。
在类的外部,只有 public 修饰的成员才能被访问,在没有涉及继承与派生时, private protected 是同
等级的,外部不允许访问。用户在类的外部可以通过 public 的方法间接访问 private protected 数据

1.2构造函数

1.2.1 初始化和清理
当我们创建对象的时候 , 这个对象应该有一个初始状态,当对象销毁之前应该销毁自己创建的一些数据。 对象的初始化和清理也是两个非常重要的安全问题,一个对象或者变量没有初始时,对其使用后果是未 知,同样的使用完一个变量,没有及时清理,也会造成一定的安全问题。c++ 为了给我们提供这种问题 的解决方案,构造函数和析构函数,这两个函数将会被编译器自动调用,完成对象初始化和对象清理工 作。 无论你是否喜欢,对象的初始化和清理工作是编译器强制我们要做的事情,即使你不提供初始化操 作和清理操作,编译器也会给你增加默认的操作,只是这个默认初始化操作不会做任何事,所以编写类就应该顺便提供初始化函数。
1.2.2 构造函数的概述
类实例化对象的时候 系统自动调用构造函数 完成对象的初始化。
如果用户不提供构造函数 编译器 会自动添加一个默认的构造函数(空函数)
1.2.3 构造函数的定义方式
构造函数名 和 类名相同,没有返回值类型(连 void 都不可以),可以有参数(可以重载),权限为
public

先给对象开辟空间(实例化) 然后调用构造函数(初始化)

class Data
{
public :
int mA ;
public :
// 无参构造函数
Data ()
{
mA = 0 ;
cout << " 无参构造函数 " << endl ;
}
// 有参构造函数
Data ( int a )
{
mA = a ;
cout << " 有参构造函数 mA=" << mA << endl ;
}
};
int main ()
{
// 隐式调用无参构造函数(推荐)
Data ob1 ;
// 显示调用无参构造函数
Data ob2 = Data ();
// 隐式调用有参构造函数(推荐)
Data ob3 ( 10 );
// 显示调用有参构造函数
Data ob4 = Data ( 10 );
// 匿名对象 ( 无参 ) 当前语句技术 立即释放
Data ();
Data ( 20 );
// 构造函数隐式转换(类中只有一个数据成员)
Data ob5 = 100 ;
}

1.2.4 提供构造函数的影响
如果用户不提供任何构造函数 编译器默认提供一个空的无参构造。
如果用户定义了构造函数(不管是有参、无参),编译器不再提供默认构造函数。

1.3 析构函数

1.3.1 析构函数的定义方式
函数名和类名称相同,在函数名前加 ~ ,没有返回值类型,没有函数形参。(不能被重载)
当对象生命周期结束的时候 系统自动调用析构函数。
先调用析构函数 再释放对象的空间。
class Data1
{
public :
int mA ;
public :
// 无参构造函数
Data1 ()
{
mA = 0 ;
cout << " 无参构造函数 " << endl ;
}
// 有参构造函数
Data1 ( int a )
{
mA = a ;
cout << " 有参构造函数 mA=" << mA << endl ;
}
// 析构函数
~Data1 ()
{
cout << " 析构函数 mA=" << mA << endl ;

  }
};
一般情况下,系统默认的析构函数就足够。但是如果一个类有 指针成员 ,这个类必须 写析构函数,释放 指针成员所指向空间。
#include<string.h>
class Data2
{
public :
char * name ;
public :
Data2 (){
name = NULL ;
}
Data2 ( char * str )
{
name = new char [ strlen ( str ) + 1 ];
strcpy ( name , str );
cout << " 有参构造 " << endl ;
}
~Data2 ()
{
if ( name != NULL )
delete [] name ;
cout << " 析构函数 " << endl ;
}
};
int main ( int argc , char * argv [])
{
Data2 ob ( "hello world" );
cout << ob . name << endl ;
return 0 ;
}
也就是说析构掉的是堆区的hello world,对象的结束只会回收对象所占的内存空间,堆区空间不是对象所占的空间!

1.4 拷贝构造函数

1.4.1 拷贝构造函数的定义
拷贝构造:本质是构造函数
拷贝构造的调用时机:旧对象 初始化 新对象 才会调用拷贝构造。
#include <iostream>
using namespace std ;
class Data
{
public :
int mA ;
public :
Data ()
{
cout << " 无参构造 " << endl ;
}
Data ( int a )
{
mA = a ;
cout << " 有参构造 mA=" << mA << endl ;
}
#if 1
// 拷贝构造的定义形式 :ob 就是旧对象的引用
Data ( const Data & ob )
{
// 一旦实现了 拷贝构造函数 必须完成赋值操作
mA = ob . mA ;
cout << " 拷贝构造函数 " << endl ;
}
#endif
~Data ()
{
cout << " 析构函数 mA=" << mA << endl ;
}
};
int main ( int argc , char * argv [])
{
Data ob1 ( 10 );
// 旧对象给新对象初始化 就会调用拷贝构造函数
Data ob2 = ob1 ;
cout << "ob2.mA =" << ob2 . mA << endl ;
return 0 ;
}
如果用户不提供拷贝构造 编译器会自动提供一个默认的拷贝构造( 完成赋值动作--浅拷贝
1.4.2 拷贝构造 和 无参构造 有参构造的关系
如果用户定义了 拷贝构造或者有参构造 都会屏蔽无参构造。
如果用户定义了 无参构造或者有参构造 不会屏蔽拷贝构造。
1.4.3 拷贝构造几种调用形式
1、旧对象给新对象初始化 调用拷贝构造
Data ob1 ( 10 );
Data ob2 = ob1 ; // 调用拷贝构造
2、给对象取别名 不会调用拷贝构造
Data ob1 ( 10 );
Data & ob2 = ob1 ; // 不会调用拷贝构造
3、普通对象作为函数参数 调用函数时 会发生拷贝构造
void func ( Data ob ) //Data ob=ob1
{
}
int main ()
{
Data ob1 ( 100 ); // 有参构造
func ( ob1 ); // 拷贝构造
}
4、函数返回值普通对象
Visual Studio 会发生拷贝构造
Qtcreater,linux不会发生

1.4.4 拷贝构造的浅拷贝和深拷贝

默认的拷贝构造 都是浅拷贝。
如果类中没有指针成员, 不用实现拷贝构造和析构函数。
如果类中有指针成员,且指向堆区空间, 必须实现析构函数释放指针成员指向的堆区空间,必须实现拷贝构造完成深拷贝动作
#include<iostream>
#include<string.h>
using namespace std ;
class Data5
{
public :
char* name ;
public :
Data5 ()
{
name = NULL ;
}
Data5 ( char* str )
{
name = new char [ strlen ( str ) + 1 ];
strcpy ( name , str );
cout << " 有参构造 name=" << name << endl ;
}
Data5 ( const Data5 & ob ) // 深拷贝
{
// 为对象的指针成员申请独立的空间
name = new char [ strlen ( ob . name ) + 1 ];
strcpy ( name , ob . name );
cout << " 拷贝构造函数 " << endl ;
}
~Data5 ()
{
cout << " 析构函数 name = " << name << endl ;
if ( name != NULL )
{
delete [] name ;
name = NULL ;
}
}
};
void test05 ()
{
Data5 ob1 (( char * ) "hello world\n" );
Data5 ob2 = ob1 ;
}
Data5 ( const Data5 & ob ) // 深拷贝
{
// 为对象的指针成员申请独立的空间
name = new char [ strlen ( ob . name ) + 1 ];
strcpy ( name , ob . name );
cout << " 拷贝构造函数 " << endl ;
}
现在假设我们 Data ob2 = ob1 ; // 调用拷贝构造;如果我们没有这个深拷贝构造的话,就会用系统默认的拷贝,就是把ob1的空间的值赋给ob2.

于是解决这个问题,就需要深拷贝构造。

Data5 ( const Data5 & ob ) // 深拷贝
{
// 为对象的指针成员申请独立的空间
name = new char [ strlen ( ob . name ) + 1 ];
strcpy ( name , ob . name );
cout << " 拷贝构造函数 " << endl ;
}
这个代码就是重新申请了空间,然后把ob的内容拷贝到新的空间,也就是他们各自指向各自的空间。所以ob1结束的时候先释放堆区在释放自己,ob2结束的时候释放自己的堆区在释放自己。
深拷贝就是让指针成员拥有独立的指向空间,不让众多对象的指针成员指向同一个空间。

1.5 初始化列表

1.5.1 对象成员

在类中定义的数据成员一般都是基本的数据类型。但是类中的成员也可以是对象,叫做对象成员。
先调用对象成员的构造函数,再调用本身的构造函数。 析构函数和构造函数调用顺序相反,先构造,后析构。
类会自动调用对象成员的 无参构造

1.5.2 初始化列表

类想调用对象成员 有参构造 必须使用 初始化列表
#include<iostream>
using namespace std;

class A
{
public:
	int mA;
public:
	A()
	{
		mA = 0;
		cout << "A的无参构造" << endl;
	}
	A(int a)
	{
		mA = a;
		cout << "A的有参构造" << endl;
	}
	~A()
	{
		cout << "A的析构函数" << endl;
	}
};
class B
{
public:
	int mB;
	A ob;//成员对象
public:
	B()
{
	cout << "B类的无参构造" << endl;
}
	  //初始化列表 成员对象 必须使用对象名+() 重要
	  B(int a, int b) : ob(a)
	  {
		  mB = b;
		  cout << "B类的有参构造" << endl;
	  }
	  ~B()
	  {
		  cout << "B的析构函数" << endl;
	  }
};
int main(int argc, char* argv[])
{
	B ob1(10, 20);
	cout << "mA =" << ob1.ob.mA << ", mB =" << ob1.mB << endl;
	return 0;
}

而如果这样写,就会调用A的无参构造

1.6 explicit关键字

c++ 提供了关键字 explicit ,禁止通过构造函数进行的隐式转换。声明为 explicit 的构造函数不能在隐式转 换中使用。 注意explicit 用于修饰构造函数 , 防止隐式转化。 是针对单参数的构造函数 ( 或者除了第一个参数外其余参 数都有默认值的多参构造) 而言。
#include<iostream>
using namespace std;

class MyString {
public:
	explicit MyString(int n) {
		cout << "MyString(int n)!" << endl;
	}
	MyString(const char* str) {
		cout << "MyString(const char* str)" << endl;
	}
};
int main() {
	//给字符串赋值?还是初始化?
	//MyString str1 = 1;
	MyString str2(10);
	//寓意非常明确,给字符串赋值
	MyString str3 = "abcd";
	MyString str4("abcd");
	return 0;
}

MyString str1 = 1;  隐式转换,看的是后面1的类型!寻找1的类型为整型,就会去找MyString(int n)来执行,但是会发现这种书写容易让人造成歧义,因此加上explicit MyString(int n)这种写法就禁止让程序员出现MyString str1 = 1;这种写法。

而下面的MyString str3 = "abcd";寓意比较明确就没加!

1.7 类的对象数组

对象数组:本质是数组 数组的每个元素是对象。
#include<iostream>
using namespace std;

class A
{
public:
	int mA;
public:
	A()
	{
		mA = 0;
		cout << "A的无参构造 mA=" << mA << endl;
	}
	A(int a)
	{
		mA = a;
		cout << "A的有参构造mA=" << mA << endl;
	}
	~A()
	{
		cout << "A的析构函数 mA = " << mA << endl;
	}
};
int main()
{
	//对象数组 每个元素都会自动调用构造和析构函数
	//对象数组不初始化 每个元素 调用无参构造
	A arr1[5];
	//对象数组的初始化 必须显示使用有参构造 逐个元素初始化
	A arr2[5] = { A(10),A(20),A(30),A(40),A(50) };
	int n = sizeof(arr2) / sizeof(arr2[0]);
	int i = 0;
	for (i = 0; i < n; i++)
	{
		cout << arr2[i].mA << " ";
	}
	cout << endl;
}

这里析构会先析构  A arr2[5] = { A(10),A(20),A(30),A(40),A(50) };,其次才是   A arr1[5];,因为两个数组是同级别的,栈先进后出,然后   A arr2[5] = { A(10),A(20),A(30),A(40),A(50) };这里面的元素也是同级别的,于是显示先进后出!

1.8 动态对象创建

1.8.1 动态创建的概述

当我们创建数组的时候,总是需要提前预定数组的长度,然后编译器分配预定长度的数组空间,在使用 数组的时,会有这样的问题,数组也许空间太大了,浪费空间,也许空间不足,所以对于数组来讲,如 果能根据需要来分配空间大小再好不过。 所以动态的意思意味着不确定性。 为了解决这个普遍的编程问 题,在运行中可以创建和销毁对象是最基本的要求。当然c 早就提供了动态内存分配( dynamic memory allocation) , 函数 malloc free 可以在运行时从堆中分配存储单元。 然而这些函数在 c++ 中不 能很好的运行,因为它不能帮我们完成对象的初始化工作。

1.8.2 c语言的方式创建动态对象

当创建一个 c++ 对象时会发生两件事 :
1. 为对象分配内存
2. 调用构造函数来初始化那块内存 第一步我们能保证实现,需要我们确保第二步一定能发生。 c++ 强迫我们这么做是因为使用未初始化的对象是程序出错的一个重要原因。 C 动态分配内存方法为了在运行时动态分配内存,c 在他的标准库中提供了一些函数 ,malloc 以及它的变种 calloc realloc, 释放内存的free, 这些函数是有效的、但是原始的,需要程序员理解和小心使用。为了使用 c 的动态内存分配函数在堆上创建一个类的实例,我们必须这样做:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class Person {
public:
	Person() {
		mAge = 20;
		pName = (char*)malloc(strlen("john") + 1);
		strcpy(pName, "john");
	}
	void Init() {
		mAge = 20;
		pName = (char*)malloc(strlen("john") + 1);
		strcpy(pName, "john");
	}
	void Clean() {
		if (pName != NULL) {
			free(pName);
		}
	}
public:
	int mAge;
	char* pName;
};
int main() {
	//分配内存
	Person* person = (Person*)malloc(sizeof(Person));
	if (person == NULL) {
		return 0;
	}
	//调用初始化函数
	person->Init();
	//清理对象
	person->Clean();
	//释放person对象
	free(person);
	return 0;
}
在c++看来c存在的问题:
1) 程序员必须确定对象的长度。 (sizeof(Person)
2) malloc 返回一个 void 指针, c++ 不允许将 void赋值给其他任何指针,必须强转。 (Person*)
3) malloc可能申请内存失败,所以必须判断返回值来确保内存分配成功。  
 if (person == NULL) {
        return 0;
    }
4) 用户在使用对象之前必须记住对他初始化,构造函数不能显示调用初始化 ( 构造函数是由编译器调用 ) ,用 户有可能忘记调用初始化函数。
//调用初始化函数
person->Init();
//清理对象
person->Clean();
//释放person对象
free(person);
c 的动态内存分配函数太复杂,容易令人混淆,是不可接受的, c++中我们推荐使用运算符new 和
delete。

1.8.3 new创建动态对象

C++ 中解决动态内存分配的方案是把创建一个对象所需要的操作都结合在一个称为 new 的运算符里。当 用new 创建一个对象时, 它就在堆里为对象分配内存并调用构造函数完成初始化。
Person* person = new Person;
New 操作符能确定在调用构造函数初始化之前内存分配是成功的,所有不用显式确定调用是否成功。现在我们发现在堆里创建对象的过程变得简单了,只需要一个简单的表达式,它带有内置的长度计算、类型转换和安全检查。这样在堆创建一个对象和在栈里创建对象一样简单

1.8.4 delete释放动态对象

new 表达式的反面是 delete 表达式。 delete表达式先调用析构函数,然后释放内存。而c中的free不会调用析构函数,直接会释放内存!
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class Person {
public:
	Person() {
		cout << "无参构造函数!" << endl;
		pName = new char[strlen("undefined") + 1];
		strcpy(pName, "undefined");
		mAge = 0;
	}
	Person(char* name, int age) {
		cout << "有参构造函数!" << endl;
		pName = new char[strlen(name) + 1];
		strcpy(pName, name);
		mAge = age;
	}
	void ShowPerson() {
		cout << "Name:" << pName << " Age:" << mAge << endl;
	}
	~Person() {
		cout << "析构函数!" << endl;
		if (pName != NULL) {
			delete[] pName;
			pName = NULL;
		}
	}
public:
	char* pName;
	int mAge;
};
int main() {
	Person* person1 = new Person;//会调用无参构造
	Person* person2 = new Person("John", 33);//会调用有参构造
	person1->ShowPerson();
	person2->ShowPerson();
	delete person1;
	delete person2;
}

1.8.5 动态对象数组

当创建一个对象数组的时候,必须对数组中的每一个对象调用构造函数,除了在栈上可以聚合初始化, 必须提供一个默认的构造函数!
class Person {
public:
	Person() {
		pName = NULL;
		mAge = 0;
	}
	Person(char* name, int age) {
		pName = new char[strlen(name) + 1];
		strcpy(pName, name);
		mAge = age;
	}
	~Person() {
		if (pName != NULL) {
			delete[] pName;
		}
	}
public:
	char* pName;
	int mAge;
};
void test() {
	//栈聚合初始化
	Person person[] = { Person("john", 20), Person("Smith", 22) };
	cout << person[1].pName << endl;
	//创建堆上对象数组必须提供构造函数
	Person* workers = new Person[20];
	delete[] workers;
}

Person* workers = new Person[20];
delete[] workers;

因为new的时候有[],所以delete也要有!

1.9 静态成员

在类定义中,它的成员(包括成员变量和成员函数),这些成员可以用关键字 static 声明为静态的,称为 静态成员。 不管这个类创建了多少个对象,静态成员只有一个拷贝,这个拷贝被所有属于这个类的对象 共享。

1.9.1 静态成员变量

static 修饰的静态成员 属于类而不是对象。 (所有对象共享 一份 静态成员数据)
static修饰的成员 定义类的时候 必须分配空间。
static修饰的静态成员数据 必须类中定义 类外初始化。
class Data
{
public:
	int a;//普通成员数据
	//类中定义
	static int b;//静态成员数据
};
//类外初始化
int Data::b = 100;//不用加static
void test01()
{
	//静态成员数据 通过类名称直接访问(属于类)
	cout << Data::b << endl;
	//静态成员数据 通过对象访问(共享)
	Data ob1;
	cout << ob1.b << endl;//100
	ob1.b = 200;
	Data ob2;
	ob2.b = 300;
	cout << Data::b << endl;//300
}
案例 1 :使用静态成员数据 统计对象的个数
class Data2
{
public:
	int mA;
	static int count;
public:
	Data2()
	{
		count++;
	}
	Data2(int a)
	{
		mA = a;
		count++;
	}
	Data2(const Data2& ob)
	{
		count++;
	}
	~Data2()
	{
		count--;
	}
};
int Data2::count = 0;
void test02()
{
	Data2 ob1;
	Data2 ob2(10);
	Data2 ob3 = ob2;
	cout << "对象个数:" << Data2::count << endl;//3
	{
		Data2 ob4;
		Data2 ob5;
		cout << "对象个数:" << Data2::count << endl;//5
	}
	cout << "对象个数:" << Data2::count << endl;//3
}

1.9.2 静态成员函数

静态成员函数 是属于类 而不是对象(所有对象 共享)
class Data
{
	static void func()//静态成员函数
	{
	}
}

说白了 static  静态成员函数就是为了能够没有进行对象调用也可以获取到私有的变量值!

静态成员函数 可以直接通过类名称访问

静态成员函数内 只能操作静态成员数据。

1.9.3 单例模式设计

单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模 式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

#include <iostream>
using namespace std;
class SingleTon//单例模式
{
	//构造私有化 防止实例化其他对象
private:
	SingleTon() {
		count = 0;
		cout << "构造" << endl;
	}
	SingleTon(const SingleTon & ob) {
		count = 0;
	}
	~SingleTon()
	{
		cout << "析够" << endl;
	}
private:
	//const防止p 在类内部 被修改指向
	static SingleTon* const p;//保存唯一的实例地址
	int count;//统计任务执行次数
public:
	static SingleTon* getSingleTon(void)//获取唯一的实例地址
	{
		return p;
	}
	//用户自定义 任务函数
	void printString(char* str)
	{
		count++;
		cout << "当前第" << count << "次任务打印:" << str << endl;
	}
};
SingleTon* const SingleTon::p = new SingleTon;//创建唯一的实例
int main(int argc, char* argv[])
{
	//获取单例的地址
	SingleTon* p1 = SingleTon::getSingleTon();
	p1->printString("离职证明1");
	p1->printString("学历证明1");
	p1->printString("学位证明1");
	p1->printString("身份证明1");
	SingleTon* p2 = SingleTon::getSingleTon();
	p2->printString("离职证明2");
	p2->printString("学历证明2");
	p2->printString("学位证明2");
	p2->printString("身份证明2");
	return 0;
}

1.10 c++面向对象模型

1.10.1 成员变量和函数的存储

c++ 实现了 封装 数据 处理数据的操作 ( 函数 )” 是分开存储的。 c++ 中的非静态数据成员直接内含在 类对象中,成员函数虽然内含在class 声明之内,却不出现在对象中。 每一个非内联成员函数只会诞生一 份函数实例。
sizeof(Data1)的大小只是 a b所占空间大小(类的对象所占空间大小)。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class MyClass01 {
public:
	int mA;
};
class MyClass02 {
public:
	int mA;
	static int mB;
};
class MyClass03 {
public:void printMyClass() {
	cout << "hello world!" << endl;
}
public:
	int mA;
	static int mB;
};
class MyClass04 {
public:
	void printMyClass() {
		cout << "hello world!" << endl;
	}
	static void ShowMyClass() {
		cout << "hello world!" << endl;
	}
public:
	int mA;
	static int mB;
};
int main() {
	MyClass01 mclass01;
	MyClass02 mclass02;
	MyClass03 mclass03;
	MyClass04 mclass04;
	cout << "MyClass01:" << sizeof(mclass01) << endl; //4
	//静态数据成员并不保存在类对象中
	cout << "MyClass02:" << sizeof(mclass02) << endl; //4
	//非静态成员函数不保存在类对象中
	cout << "MyClass03:" << sizeof(mclass03) << endl; //4
	//静态成员函数也不保存在类对象中
	cout << "MyClass04:" << sizeof(mclass04) << endl; //4
	return 0;
}
通过上面的案例,我们可以的得出: C++ 类对象中的变量和函数是分开存储。

1.10.2 this指针

1this指针工作原理
通过上例我们知道, c++ 的数据和操作也是分开存储,并且每一个非内联成员函数只会诞生一份函数实 例,也就是说多个同类型的对象会共用一块代码 那么问题是:这一块代码是如何区分那个对象调用自己的呢?
c++ 通过提供特殊的对象指针, this 指针,解决上述问题。 this 指针指向被调用的成员函数所属的对象。
成员函数通过 this 指针即可知道操作的是那个对象的数据。 This 指针是一种隐含指针,它隐含于每个类的
非静态成员函数中。 This 指针无需定义,直接使用即可。
注意:静态成员函数内部没有 this 指针,静态成员函数不能操作非静态成员变量
2、函数形参和成员同名可以使用this指针解决。
3、this来完成链式操作

1.10.3 const修饰成员函数

const 修饰的成员函数时, const 修饰 this 指针指向的内存区域,成员函数体内不可以修改本类中的任何普通成员变量, 当成员变量类型符前用mutable 修饰时例外
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class Data
{
public:
	int a;
	int b;
	mutable int c;
public:
	Data(int a, int b, int c)
	{
		this->a = a;
		this->b = b;
		this->c = c;
	}
	//const 修饰成员函数为只读(该成员函数不允许对 成员数据 赋值) mutable修饰的成员除外
	void showData(void) const
	{
		//a = 100;//err
		c = 100;
		cout << a << " " << b << " " << c << endl;
	}
};
int main()
{
	Data ob1(10, 20, 30);
	ob1.showData();
}

1.11 友元

类的主要特点之一是数据隐藏,即类的私有成员无法在类的外部 ( 作用域之外 ) 访问。但是,有时候需要在类的外部访问类的私有成员,怎么办?
解决方法是使用友元函数,友元函数是一种特权函数, c++ 允许这个特权函数访问私有成员。这一点从 现实生活中也可以很好的理解: 比如你的家,有客厅,有你的卧室,那么你的客厅是Public 的,所有来 的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去,但是呢,你也可以允许你的闺蜜好基友进去。 程序员可以把一个全局函数、某个类中的成员函数、甚至整个类声明为友元。

1.11.1 友元的语法

使用 friend 关键字声明友元。
friend 关键字只出现在声明处,一个函数或者类 作为了另一个类的友元 那么这个函数或类 就可以直接 访问 另一个类的私有数据。
友元 重要用在运算符重载上。

1.11.2 普通全局函数作为类的友元

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class Room
{
	friend void visiting01(Room& room);
private:
	string bedRoom;//卧室
public:
	string setingRoom;//客厅
public:
	Room(string bedRoom, string setingRoom)
	{
		this->bedRoom = bedRoom;
		this->setingRoom = setingRoom;
	}
};
//普通全局函数
void visiting01(Room& room)
{
	cout << "访问了" << room.setingRoom << endl;
	cout << "访问了" << room.bedRoom << endl;
}
int main(int argc, char* argv[])
{
	Room room("卧室", "客厅");
	visiting01(room);
	return 0;
}

1.11.3 类的某个成员函数 作为另一个类的友元

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class Room;//向前声明 只能说明类名称
class goodGay
{
public:
	void visiting01(Room& room);
	void visiting02(Room& room);
}; class Room
{
	friend void goodGay::visiting02(Room& room);
private:
	string bedRoom;//卧室
public:
	string setingRoom;//客厅
public:
	Room(string bedRoom, string setingRoom)
	{
		this->bedRoom = bedRoom;
		this->setingRoom = setingRoom;
	}
};
void goodGay::visiting01(Room& room)
{
	cout << "访问了" << room.setingRoom << endl;
	//cout<<"访问了"<<room.bedRoom<<endl;
}
void goodGay::visiting02(Room& room)
{
	cout << "好基友张三访问了" << room.setingRoom << endl;
	cout << "好基友张三访问了" << room.bedRoom << endl;
}
int main(int argc, char* argv[])
{
	Room room("卧室", "客厅");
	goodGay ob;
	ob.visiting01(room);
	ob.visiting02(room);
	return 0;
}

1.11.4 整个类作为 另一个类的友元

这个类的所有成员函数 都可以访问另一个类的私有数据 .
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class Room;//向前声明 只能说明类名称
class goodGay
{
public:
	void visiting01(Room& room);
	void visiting02(Room& room);
};
class Room
{
	friend class goodGay;
private:
	string bedRoom;//卧室
public:
	string setingRoom;//客厅
public:
	Room(string bedRoom, string setingRoom) {
		this->bedRoom = bedRoom;
		this->setingRoom = setingRoom;
	}
};
void goodGay::visiting01(Room& room)
{
	cout << "访问了" << room.setingRoom << endl;
	cout << "访问了" << room.bedRoom << endl;
}
void goodGay::visiting02(Room& room)
{
	cout << "好基友访问了" << room.setingRoom << endl;
	cout << "好基友访问了" << room.bedRoom << endl;
}
int main(int argc, char* argv[])
{
	Room room("卧室", "客厅");
	goodGay ob;
	ob.visiting01(room);
	ob.visiting02(room);
	return 0;
}

1.11.5 友元的注意事项

1 .友元关系不能被继承。
2 .友元关系是单向的,类 A 是类 B 的朋友,但类 B 不一定是类 A 的朋友。
3 .友元关系不具有传递性。类 B 是类 A 的朋友,类 C 是类 B 的朋友,但类 C 不一定是类 A 的朋友

1.11.6 友元案例(遥控器的类)

请编写电视机类,电视机有开机和关机状态,有音量,有频道,提供音量操作的方法,频道操作的方 法。由于电视机只能逐一调整频道,不能指定频道,增加遥控类,遥控类除了拥有电视机已有的功能, 再增加根据输入调台功能。
提示:遥控器可作为电视机类的友元类
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

#include <iostream>
using namespace std;
class TV;
//遥控器的类作为TV的友元
class Remote
{
private:
	TV* p;
public:
	Remote(TV* p);
	void offOrOn(void);
	void upVolume(void);
	void downVolume(void);
	void upChannel(void); void downChannel(void);
	void showTv(void);
	void setChannel(int channel);
};
class TV
{
	friend class Remote;
	enum { OFF, ON };
	enum { minVol, maxVol = 10 };
	enum { minChan, maxChan = 25 };
private:
	int state;
	int volume;
	int channel;
public:
	TV()
	{
		state = OFF;
		volume = minVol;
		channel = minChan;
	}
	void offOrOn(void);
	void upVolume(void);
	void downVolume(void);
	void upChannel(void);
	void downChannel(void);
	void showTv(void);
};
int main(int argc, char* argv[])
{
	TV tv;
	Remote re(&tv);
	re.offOrOn();
	re.upVolume();
	re.upVolume();
	re.setChannel(10);
	re.showTv();
	return 0;
}
void TV::offOrOn()
{
	state = (state == OFF ? ON : OFF);
}
void TV::upVolume()
{
	if (volume == maxVol)
	{
		cout << "音量已经最大" << endl;
		return;
	}volume++;
}
void TV::downVolume()
{
	if (volume == minVol)
	{
		cout << "音量已经最小" << endl;
		return;
	}
	volume--;
}
void TV::upChannel()
{
	if (channel == maxChan)
	{
		cout << "频道已经最大" << endl;
		return;
	}
	channel++;
}
void TV::downChannel()
{
	if (channel == minChan)
	{
		cout << "频道已经最小" << endl;
		return;
	}
	channel--;
}
void TV::showTv()
{
	cout << "当前电视机的状态:" << (state == OFF ? "关" : "开") << endl;
	cout << "当前电视机的音量:" << volume << endl;
	cout << "当前电视机的频道:" << channel << endl;
}
Remote::Remote(TV* p)
{
	this->p = p;
}
void Remote::offOrOn()
{
	p->offOrOn();
}
void Remote::upVolume()
{
	p->upVolume();
}
void Remote::downVolume()
{
	p->downVolume();
}
void Remote::upChannel()
{
	p->upChannel();
}
void Remote::downChannel()
{
	p->downChannel();
}
void Remote::showTv()
{
	p->showTv();
}
void Remote::setChannel(int channel)
{
	if (channel >= TV::minChan && channel <= TV::maxChan)
	{
		p->channel = channel;
	}
	else
	{
		cout << "频道" << channel << "不在有效范围内" << endl;
	}
}

1.11.7 设计动态数组类案例

1.12 运算符重载

1.12.1 运算符重载基本概念

运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
语法: 定义重载的运算符就像定义函数,只是该函数的名字是 operator@, 这里的 @ 代表了被重载的运算 符。
思路:
1 、弄懂运算符的运算对象的个数。(个数决定了 重载函数的参数个数)
2 、识别运算符左边的运算对象 是类的对象 还是其他 .
类的对象:全局函数实现(不推荐) 成员函数实现(推荐,少一个参数)
其他:只能是全局函数实现

1.12.2 重载<<运算符(全局函数实现)

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

#include <string>
using namespace std;
class Person
{
	friend ostream& operator<<(ostream& out, Person& ob);
private:
	int num;
	string name;
	float score;
public:
	Person() {}
	Person(int num, string name, float score) :num(num), name(name), score(score) {}
};
//全局函数重载operator <<
ostream & operator<<(ostream & out, Person & ob)
{
	out << ob.num << " " << ob.name << " " << ob.score << endl;
	return out;
}
int main(int argc, char* argv[])
{
	Person lucy(100, "lucy", 99.8f);
	Person bob(101, "bob", 99.8f);
	cout << lucy << bob << endl;//operator<<(cout, lucy);
	return 0;
}
如果使用全局函数 重载运算符 必须将全局函数设置成友元。

1.12.3 重载>>运算符(全局函数实现)

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

#include <iostream>
#include <string>
using namespace std;
class Person
{
	friend ostream& operator<<(ostream& out, Person& ob);
	friend istream& operator>>(istream& in, Person& ob); private:
		int num;
		string name;
		float score;
public:
	Person() {}
	Person(int num, string name, float score) :num(num), name(name), score(score) {}
};
//全局函数重载operator<<
ostream& operator<<(ostream& out, Person& ob)
{
	out << ob.num << " " << ob.name << " " << ob.score << endl;
	return out;
}
//全局函数重载operator>>
istream& operator>>(istream& in, Person& ob)
{
	in >> ob.num >> ob.name >> ob.score;
	return in;
}
int main(int argc, char* argv[])
{
	Person lucy;
	Person bob;
	cin >> lucy >> bob;
	cout << lucy << bob << endl;
	return 0;
}

1.12.4 可以重载的运算符

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2013年12月13日 Go生态洞察:Go在App Engine上的工具、测试和并发

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

2023年中国聚氨酯树脂涂料需求量、市场规模及行业趋势分析[图]

聚氨酯是一种新兴的有机高分子材料&#xff0c;被誉为“第五大塑料”&#xff0c;因其卓越的性能而被广泛应用于国民经济众多领域。产品应用领域涉及轻工、化工、电子、纺织、医疗、建筑、建材、汽车、国防、航天、航空等。2022年中国聚氨酯产量已达1600万吨。 2012-2022年中国…

华为防火墙 DMZ 设置

DMZ 是英文"Demilitarized Zone"的缩写&#xff0c;中文名称为"隔离区" 它是为了解决安装防火墙后外部网络不能访问内部网络服务器的问题&#xff0c;而设立的一个位于内部网络与外部网络之间的缓冲区&#xff0c;在这个网络区域内可以放置一些公开的服务…

半导体电学特性IV+CV测试系统—1200V半导体参数分析仪

概述&#xff1a; SPA-6100半导体参数分析仪是武汉普赛斯自主研发、精益打造的一款半导体电学特性测试系统&#xff0c;具有高精度、宽测量范围、快速灵活、兼容性强等优势。产品可以同时支持DC电流-电压(I-V)、电容-电压(C-V)以及高流高压下脉冲式I-V特性的测试&#xff0c;旨…

asp.net勤工助学管理系统VS开发sqlserver数据库web结构c#编程计算机网页项目

一、源码特点 asp.net 勤工助学管理系统 是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 系统运行视频 https://www.bilibili.com/video/BV1Sz4y1F7GP/ 二、功能介绍 本系统使用Microsoft Visual Studio…

使用遗传算法优化的BP神经网络实现自变量降维

大家好&#xff0c;我是带我去滑雪&#xff01; 在现实生活中&#xff0c;实际问题很难用线性模型进行描述。神经网络的出现大大降低了模型建立的难度和工作量。只需要将神经网络当作一个黑箱子&#xff0c;根据输入和输出数据&#xff0c;神经网络依据相关的学习规则&#xff…

MAX/MSP SDK学习03:Atoms and Messages的使用

今天终于把Message消息选择器看得有点头绪了&#xff0c;主要是这个官方英文文档理解起来有点抽象。 编写IsMatchABC自定义Object&#xff0c;要求&#xff1a; ①若左入口&#xff08;入口0&#xff09;收到 "int" 型消息&#xff0c;则从出口发送数值 "888&q…

在ITSM中,实施变更管理的重要因素!

在ITSM管理中&#xff0c;变更管理是不可或缺的一步。在当今快速变革的商业环境中&#xff0c;组织需要不断地进行变更以适应市场的需求和竞争的压力。 然而&#xff0c;引入变更并成功地实施变更并不容易。变更管理是一种系统化和结构化的方法&#xff0c;旨在确保变更顺利进…

用二维码进行人员管理,人员信息一目了然

对于人员实名管理、来访登记、安全教育等需求&#xff0c;可以在草料二维码上搭建人员信息管理系统。除了扫码查看个人信息、身份证件、资格证书、劳务合同等人员档案&#xff0c;还可以组合表单、状态等功能组件&#xff0c;在二维码上展示证件状态&#xff0c;更新人员的奖惩…

Python量化--诺贝尔奖获得者布莱克-斯科尔斯期权定价公式在日间交易中的应用

“我们不能让你在不了解一点期权定价基础知识的情况下离开麻省理工学院,”Andrew Lo 教授在麻省理工学院的 15.401 金融理论课上对学生们说道。虽然我还不是麻省理工学院的学生,但这句话给了我一个直觉:期权定价一定极其重要。由于像麻省理工学院毕业生这样的精英金融人士都…

计算机网络的OSI七层模型

目录 1、OSI七层模型是什么 1.1 物理层&#xff08;Physical Layer&#xff09; 1.2 数据链路层&#xff08;Data Link Layer&#xff09; 1.3 网络层&#xff08;Network Layer&#xff09; 1.4 传输层&#xff08;Transport Layer&#xff09; 1.5 会话层&#xff08;S…

SecureCRT -- 使用说明

【概念解释】什么是SSH&#xff1f; SSH的英文全称是Secure Shell 传统的网络服务程序&#xff0c;如&#xff1a;ftp和telnet在本质上都是不安全的&#xff0c;因为它们在网络上用明文传送口令和数据&#xff0c;别有用心的人非常容易就可以截获这些口令和数据。而通过使用SS…

人工智能基础_机器学习045_逻辑回归的梯度下降公式推导_更新公式---人工智能工作笔记0085

然后我们上面有了逻辑回归的损失函数,以后,我们再来看 逻辑回归的梯度下降公式 可以看到上面是逻辑回归的梯度下降公式,这里的阿尔法是学习率,这里的 后面的部分是梯度也就是步长,这个阿尔法是,通过调节这个来控制梯度下降的快和慢对吧 然后我们再来看逻辑回归 可以看到这里…

武汉凯迪正大—盐雾试验机

产品概述 武汉凯迪正大KDYD-YW盐雾试验箱乃针对各种材质表面处理&#xff0c;包含涂料、电镀、有机及无机皮膜&#xff0c;阳极处理&#xff0c;防锈油等防腐处理后测试其耐腐蚀性&#xff0c;从而确立产品的质量。 产品特点 1、结构紧凑&#xff0c;体积小、携带方便&#…

零基础想系统地学习金融学、量化投资、数据分析、python,需要哪些课程、书籍?有哪些证书可以考?

曾经我也是零基础小白&#xff0c;题主想走的路&#xff0c;我已经走过啦&#xff5e;作为一名CFA持证人和管理因子投资的量化策略的投资组合经理&#xff0c;我把这些年积累的干货跟大家分享。 量化投资是金融学的一部分&#xff0c;量化投资&#xff08;跟量化交易的概念有部…

SQL常见函数整理 —— LAG() 向上偏移

1. 用法 窗口函数&#xff0c;用于访问窗口中当前行之前的行的数据。该函数可以根据需要计算当前行之前的值&#xff0c;使我们能够轻松地比较不同行之间的差异和变化。 2. 基本语法 LAG(column, offset, default_value) OVER (ORDER BY column)column&#xff1a;代表在返回…

鸿蒙原生应用/元服务开发-AGC分发如何配置版本信息(上)

1.配置HarmonyOS应用的“发布国家或地区”。 2.设置是否为开放式测试版本。 注意&#xff1a;HarmonyOS应用开放式测试当前仅支持手机、平板、智能手表。如开发者想发布为开放式测试版本&#xff0c;选择“是”。正式发布的版本请选择“否”。 3.在“软件版本”下点击“软件包…

获取1688店铺所有商品、店铺列表api

返回数据格式&#xff1a; 请求链接 {"user": [],"items": {"item": [{"num_iid": "738354436678","title": "国产正品i13 promax全网通5G安卓智能手机源头厂家批发手机","pic_url": "…

c语言:十进制转任意进制

思路&#xff1a;如十进制转二进制 就是不断除二求余在除二求余&#xff0c;然后将余数从下到写出来&#xff0c;这样&#xff0c;10011100就是156的二进制 这里举例一个六进制的代码&#xff1a; #define _CRT_SECURE_NO_WARNINGS #include<stdio.h>int main() {int …

uniapp+vue3使用pinia,安卓端报错白屏

报错内容&#xff1a; reportJSException >>>> exception function:createInstanceContext, exception:white screen cause create instanceContext failed,check js stack ->at useStore2 (app-service.js:1487:15)at (app-service.js:1714:17)at (app-serv…
最新文章