【黑马程序员】C++封装、对象特性、友元

文章目录

  • 类和对象
  • 封装
    • 封装的意义
    • struct和class的区别
    • 成员属性设置为私有
    • 封装练习
      • 设计立方体类
      • 点和圆的关系
  • 对象的初始化和清理
    • 构造函数和析构函数
      • 构造函数
      • 析构函数
    • 构造函数的分类及调用
    • 拷贝构造函数的调用时机
    • 构造函数调用规则
    • 深拷贝与浅拷贝
      • 浅拷贝
      • 深拷贝
    • 初始化列表
    • 类对象作为类的成员
    • 静态成员
      • 静态成员变量
      • 静态成员函数
  • C++ 对象模型和this指针
    • 成员变量和成员函数分开存储
    • this指针
      • `this` 指针具有以下特点:
      • this指针用途
    • 空指针访问成员函数
    • const修饰成员函数
      • 常函数
      • 常对象
  • 友元
    • 使用全局函数做友元
    • 类做友元
    • 成员函数做友元

20240214

类和对象

  • C++面向对象三大特性:封装、继承、多台

  • c++中万事皆对象

封装

封装的意义

  • 将对象
#include 

using namespace std;

const double PI = 3.14;
//定义一个圆类
class Circle{
// 访问权限
public:
	// 属性
	// 半径
	int m_r;
	// 行为
	// 获取圆的周长
	double getPerimeter() {
		return 2*PI*m_r;
	}
};

int main(){
	// 使用类初始化一个对象
	Circle c;
	// 对象属性赋值
	c.m_r = 2;
	// 调用对象方法获取圆周长
	cout << "圆的周长:" << c.getPerimeter() << endl;
	return 0;
}
  • 控制访问权限

    • public 公共权限,成员在类内可以访问,类外也可以访问

    • protect 保护权限,成员在类内可以访问,类外不可以访问

    • private 私有权限,成员在类内可以访问,类外不可以访问

#include 
#include 

using namespace std;

class Person{
public:
	string name;
protected:
	int age;
private:
	string phone;
public:
	// 类内访问
	void func(){
		name="zs";
		age = 12;
		phone="12345";
	}
};

int main(){
	Person p;
    // 类外访问
	p.name = "ls";
	// error: 'age' is a protected member of 'Person'
	// p.age = 18;
	// error: 'phone' is a private member of 'Person'
	//p.phone = 20;
	return 0;
}

struct和class的区别

  • 唯一区别就是默认的访问权限不同

  • struct默认的访问全向为public权限

  • classs默认的访问全向为private权限

struct P1{
	int age;    // 默认为public权限
};

class P2 {
	int age;    // 默认为private权限
};

int main(){
	// 实例化struct类对象
	P1 p1;
	// 实例化class类对象
	P2 p2;
	p1.age = 1;
	// error: 'age' is a private member of 'P2'
	// p2.age = 2;
	return 0;
}

成员属性设置为私有

  • 优点:

    • 可以自己控制读写权限

    • 对于写权限可以判断数据的有效性

#include 
#include 

using namespace std;

class Person{
public:
	void setName(string name){
		m_name=name;
	}
	string getName(){
		return m_name;
	}
	void setAge(int age){
		// 判断数据有效性
		if (age > 100 || age < 0) {
			return;
		}
		m_age = age;
	}
	int getAge(){
		return m_age;
	}
private:
	string m_name;	// 可读可写
	int m_age;	// 可读
	string m_password;	// 不可读不可写
};

int main(){
	Person p1;
	p1.setName("zs");
	p1.getName();
	p1.setAge(18);
	p1.getAge();
	return 0;
}

封装练习

设计立方体类

  • 求出立方体的面积和体积
#include 

using namespace std;

class Cube {
public:
	void setEdge(int edge){
		m_edge = edge;
	}

	// 计算面积
	int calcArea() {
		return 6 * m_edge * m_edge;
	}
	// 计算体积
	int calcVolume() {
		return m_edge*m_edge*m_edge;
	}
	// 判断两个立方体是否相同
	bool isSameCube(int edge) {
		return m_edge=edge;
	}
private:
	int m_edge;
};

int main(){
	Cube c1;
	c1.setEdge(2);
	cout << "c1立方体的面积是:" << c1.calcArea() << endl;
	cout << "c1立方体的体积是:" << c1.calcVolume() << endl;
	return 0;
}

点和圆的关系

  • 设计一个圆类和点类,判断点和圆的关系

  • 圆头文件编写circle.hpp

#pragma once
#include "point.hpp"

class Circle{
public:
	void setRadio(int r, int x, int y);
	int getR();
	Point getPoint();
private:
	int m_r;	// 半径
	Point p;	// 圆心
};
  • 点头文件编写point.hpp
#pragma once

class Point {
public:
	void setX(int x);

	void setY(int y);
	int getX();
	int getY();
private:
	int m_x;	// 横坐标
	int m_y;	// 纵坐标
};
  • 源文件编写point_and_circle.cpp
#include 
#include 
#include "circle.hpp"
#include "point.hpp"

using namespace std;

void Point::setX(int x) {
	m_x = x;
}

void Point::setY(int y) {
	m_y = y;
}
int Point::getX() {
	return m_x;
}
int Point::getY() {
	return m_y;	
}

void Circle::setRadio(int r, int x, int y) {
	m_r = r;
	p.setX(x);
	p.setY(y);
}
int Circle::getR() {
	return m_r;
}
Point Circle::getPoint() {
	return p;
}

void judgePointAndCircleRelation(Circle& c, Point& p) {
	// 计算点到圆心的距离
	int d = sqrt((p.getX()-c.getPoint().getX())^2+(p.getY()-c.getPoint().getY())^2);
	int r = c.getR();
	if (d == r){
		cout << "点在圆上" << endl;
	} else if (d > r) {
		cout << "点在圆外" << endl;
	} else {
		cout << "点在圆外" << endl;
	}
}

int main() {
	Circle c;
	Point p;
	c.setRadio(2, 0 , 0);
	p.setX(10);
	p.setY(2);
	judgePointAndCircleRelation(c, p);
	return 0;
}

对象的初始化和清理

构造函数和析构函数

  • 由编译器自动调用

  • 如果不写构造函数和析构函数,编译器会自己提供空实现的构造函数和析构函数

构造函数

  • 主要用于创建对象时,为对象成 员赋值

  • 语法:类名() {}

    • 构造函数没有返回值,也不用写void

    • 函数名称和类名相同

    • 构造函数可以有参数,因此可以发生重载

    • 程序在调用对象时会自动调用构造函数,无需手动调用且只会调用一次

析构函数

  • 主要用于对象销毁时执行一些清理工作

  • 语法:~类名() {}

    • 析构函数没有返回值,也不用写void

    • 函数名称和类名相同,在名称前加上~

    • 构造函数不可以有参数,因此也不能发生重载

    • 程序在调用对象时会自动调用构造函数,无需手动调用且只会调用一次

#include 

using namespace std;

class Person {
public:
	Person() {
		cout << "construct" << endl;
	}
	~Person() {
		cout << "deconstruct" << endl;
	}
};


int main(){
	Person p;
	return 0;
}

构造函数的分类及调用

  • 按参数分类:有参构造、无参构造

  • 按类型分为:普通构造、拷贝构造

  • 按调用方式

    • 括号法

    • 显示法

    • 隐式转换法

#include 

using namespace std;

class Person{
public:
	Person(){
		cout << "无参构造" << endl;
	}
	Person(int age){
		m_age=age;
		cout << "有参构造" << endl;
	}
	Person(const Person& p){
		m_age=p.m_age;
		cout << "拷贝构造" << endl;
	}
	~Person(){
		cout << "析构函数" << endl;
	}
private:
	int m_age;
};

void test() {
	cout << "===括号法调用===" << endl;
	// 无参构造调用
	Person p;
	// 调用默认构造函数时不要加()
	// Person px() 编译器会将这行代码当成一个函数声明
	// Person px();
	// 有参构造调用
	Person p1(3);
	// 拷贝构
	Person p2(p1);
	cout << "===显示法调用===" << endl;
	Person pp1 = Person(1);
	Person pp2 = Person(pp1);
	// Person(10) 匿名对象,当前行执行完系统会立即回收掉匿名对象
	Person(10);
	// 不要利用拷贝构造函数初始化匿名对象,编译器会认为Person(p) ==> Person p(p),造成p变量重定义
	cout << "===隐式转换法===" << endl;
	Person p4 =10;	// 等价与Person p4=Person(10);
	Person p5 = p4;
}

int main() {
	test();
	return 0;
}

拷贝构造函数的调用时机

  • 使用一个已经创建完毕的对象来初始化另一个对象

  • 值传递的方式给函数参数传值

  • 以值方式返回局部对象

#include 

using namespace std;

class Person{
public:
	Person(){
		cout << "无参构造" << endl;
	}
	Person(int age){
		m_age=age;
		cout << "有参构造" << endl;
	}
	Person(const Person& p){
		m_age=p.m_age;
		cout << "拷贝构造" << endl;
	}
	~Person(){
		cout << "析构函数" << endl;
	}
private:
	int m_age;
};

// 使用一个已经创建的对象来初始化另一个新对象
void test01() {
	Person p(10);
	Person p1(p);
}

// 值传递方式给函数参数传值
void func(Person p) {

}

void test02() {
	Person p(10);
	func(p);
}

// 以值方式返回局部对象
Person doWork() {
	Person p(1);
	return p;
}

void test03() {
	Person p =doWork();
}


int main(){
	test01();
	test02();
	test03();
	return 0;
}

构造函数调用规则

  • 默认情况下,c++编译器至少会给一个类添加3个函数

    • 默认构造函数(无参函数体为空)

    • 默认析构函数(无参函数体为空)

    • 默认拷贝构造函数,对属性进行值拷贝

  • 构造函数调用规则

    • 如果自定义了有参构造函数,编译器不会在提供默认无参构造函数
    #include 
    
    using namespace std;
    
    class Person{
    public:
    	Person(int age){
    		m_age=age;
    		cout << "有参构造函数" << endl;
    	}
    	~Person(){
    		cout << "默认析构函数" << endl;
    	}
    	int m_age;
    };
    
    void test01(){
    	Person p; //no matching constructor for initialization of 'Person'
    }
    
    int main(){
    	test01();
    	return 0;
    }
    
    • 但是会提供默认拷贝构造
    #include 
    
    using namespace std;
    
    class Person{
    public:
    	Person(){
    		cout << "默认构造函数" << endl;
    	}
    	Person(int age){
    		m_age=age;
    		cout << "有参构造函数" << endl;
    	}
    	~Person(){
    		cout << "默认析构函数" << endl;
    	}
    	int m_age;
    };
    
    void test01(){
    	Person p;
    	p.m_age=18;
    	Person p1(p); //调用默认拷贝构造
    	cout << p1.m_age << endl;
    }
    
    int main(){
    	test01();
    	return 0;
    }
    
    • 如果自定义了拷贝构造函数,编译器不会在提供其它构造函数
#include 

using namespace std;

class Person{
public:
	Person(const Person& p){
		m_age=p.m_age;
		cout << "拷贝构造函数" << endl;
	}
	~Person(){
		cout << "默认析构函数" << endl;
	}
	int m_age;
};

void test01(){
	//no matching constructor for initialization of 'Person'
    Person p;
	Person p1(18);
}

int main(){
	test01();
	return 0;
}

深拷贝与浅拷贝

  • 如果类中有属性需要在对上开辟空间,一定要自己实现拷贝构造函数

浅拷贝

  • 简单的赋值拷贝操作

  • 浅拷贝存在问题:堆区的内存重复释放

#include 

using namespace std;

class Person{
public:
        Person(){
                cout << "默认构造" << endl;
        }
        Person(int age, int height){
                m_age = age;
                // 使用new关键字在堆上开辟
                m_height=new int(height);
                cout << "赋值拷贝构造" << endl;
        }
        ~Person(){
                if (m_height != NULL) {
                        delete m_height;
                }
                cout << "析构函数" << endl;
        }
        int m_age;
        int *m_height;
};

void test(){
        Person p(18, 170);
        // 调用默认拷贝构造函数,使用的是浅拷贝的方式,析构释放堆上的内存会出错
        Person p1(p);
}

int main(){
        test();
        return 0;
}

【黑马程序员】C++封装、对象特性、友元_第1张图片

深拷贝

  • 在堆区重新申请空间,进行拷贝操作

  • 解决深拷贝,重复释放对上内存导致的panic

#include 

using namespace std;

class Person{
public:
	Person(){
		cout << "默认构造" << endl;
	}
	Person(int age, int height){
		m_age = age;
		// 使用new关键字在堆上开辟
		m_height=new int(height);
		cout << "赋值拷贝构造" << endl;
	}
	Person (const Person& p) {
		m_age=p.m_age;
		m_height = new int(*p.m_height);
	}
	~Person(){
		if (m_height != NULL) {
			delete m_height;
		}
		cout << "析构函数" << endl;
	}
	int m_age;
	int *m_height;
};

void test1() {
	Person p(18, 170);
        // 调用自定义实现的深拷贝构造函数
        Person p1(p);
}

int main(){
	test1();
	return 0;
}

初始化列表

  • C++提供初始化列表语法,用来初始化 属性

  • 语法:构造函数():属性1(值1),属性2(值2)...

#include 
#include 

using namespace std;

class Person{
public:
	// 写死了函数必须按这种方式传递
	Person():m_name("zs"),m_age(10){
		cout << "无参初始化列表" << endl;
	}
	// 按照参数方式传入
	Person(int age, string name):m_age(age),m_name(name) {
		cout << "有参初始化列表" << endl;
	}
	~Person(){
		cout << "析构函数" << endl;
	}
	int m_age;
	string m_name;
};

int main(){
	Person p;
	cout << "姓名:" << p.m_name << " 年龄:" << p.m_age << endl;
	Person p1(18, "ls");
	cout << "姓名:" << p1.m_name << " 年龄:" << p1.m_age << endl;
	return 0;
}

类对象作为类的成员

  • C++中的成员可以是另一个类的对象,我们称该成员为对象成员

  • 构造的顺序:先调用成员的构造函数,在调用本类的构造函数,析构与构造的顺序相反

#include 
#include 

using namespace std;

class Phone{
public:
	Phone(string name):pname(name){
		cout << "Phone构造函数" << endl;
	}
	~Phone(){
		cout << "Phone析构函数" << endl;
	}
	string pname;
};

class Person{
public:
	Person(string name, string pname):m_name(name),m_phone(pname){
		cout << "Person构造函数" << endl;
	}
	~Person() {
		cout << "Person析构函数" << endl;
	}
	string m_name;
	Phone m_phone;
};

void test(){
	Person p("zs", "iphone");
}

int main(){
	test();
	return 0;
}

静态成员

  • 静态成员就是在成员变量和成员函数前面加上static关键字

  • 静态成员也是有访问权限的

静态成员变量

  • 所有对象共享同一份数据

  • 在编译阶段分配内存

  • 类内声明,类外初始化

  • 访问方式:通过对象访问;通过类名访问

#include 

using namespace std;

class Person{
public:
        // 类内声明
        static int age;
private:
        // 静态成员也是有访问权限的
        static int a;
};

// 类外初始化,Person::告诉编译器这是这个作用域下的变量
int Person::age=100;
int Person::a = 12;

void test(){
        Person p;
        cout << p.age << endl;
        Person p1;
        p1.age=18;
        cout << p.age << endl;
}

void test01(){
        // 静态成员变量不属于某个对象,所有对象都共享同一份数据,因此静态成员变量有两种访问方式
        // 方式一
        Person p;
        cout << "通过对象访问:" << p.age << endl;
        // 方式二
        cout << "通过类名访问:" << Person::age << endl;
}

void test02(){
        // 静态成员也是有访问权限的
        // error: 'a' is a private member of 'Person'
        // cout << "访问静态私有变量访问不到" << Person::a << endl;
}

int main(){
        test();
        test01();
        test02();
        return 0;
}

静态成员函数

  • 所有对象共享同一个函数

  • 静态成员函数只能访问静态成员变量

#include 

using namespace std;

class Person{
public:
        // 静态成员函数
        static void func(){
                // 静态成员函数只能访问静态变量
                m_A = 1;
                // 不能区分非静态变量是属于那个对象的成员
                // error: invalid use of member 'm_B' in static member function
                // m_B = 2;
                cout << "static void func() 调用" << endl;
        }
        static int m_A;
        int m_B;
};
int Person::m_A=1;

// 静态成员函数调用方式
void test(){
        // 使用对象调用
        Person p;
        p.func();
        // 使用类名调用
        Person::func();
}

int main(){
        test();
        return 0;
}

C++ 对象模型和this指针

成员变量和成员函数分开存储

  • 空类占用内存大小为1,用来占位

  • 非静态成员变量属于类上的对象

  • 静态成员变量、非静态成员函数(非静态成员函数也是只有一份函数实例)、静态成员函数都不属于类上的对象

#include 

using namespace std;

class A {};

class B {
	// 非静态成员变量属于类上的对象
	int a;
};

class C {
	// 非静态成员变量属于类上的对象
	int a;
	// 静态成员变量不属于类的对象上
	static int b;
};

class D {
	// 非静态成员变量属于类上的对象
	int a;
	// 静态成员变量不属于类的对象上
	static int b;
	void func(){}
};


class E {
	// 非静态成员变量属于类上的对象
	int a;
	// 静态成员变量不属于类的对象上
	static int b;
	void func(){}
	static void func1(){}
};


void test(){
	A a;
	// 空对象占用空间大小:1
	// C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
	// 每个空对象也应该有一个独一无二的内存地址
	cout << "空类的大小是:" << sizeof(a) << endl;
}

void test1(){
	B b;
	cout << "只有一个int类型非静态成员变量的类的大小是:" << sizeof(b) << endl;
}

void test2(){
	C c;
	cout << "一个int类型非静态成员变量和一个int类型的静态成员变量的类的大小是:" << sizeof(c) << endl;
}

void test3(){
	D d;
	// 普通成员函数不属于类上的对象
	cout << sizeof(d) << endl;
}

void test4() {
	E e;
	// 静态成员函数不属于类上的对象
	cout << sizeof(e) << endl;
}

int main(){
	test();
	test1();
	test2();
	test3();
	test4();
	return 0;
}

this指针

  • this指针指向被调用成员函数所属的对象

  • this指针是隐含每一个非静态成员函数的一种指针

  • this指针不需要定义,直接使用即可

this 指针具有以下特点:

  • this 指针是一个指针,它存储对象的地址。

  • this 指针是一个常量指针,不能修改它所指向的地址。

  • this 指针在非静态成员函数内部是可用的,它允许你通过 this->(*this). 访问对象的成员变量和成员函数。

  • this 指针的类型是指向类类型的常量指针。例如,如果对象属于 Person 类,则 this 的类型是 Person* const

this指针用途

  • 当形参和成员变量同名时可以使用this指针来区分

  • 在类的非静态成员函数中返回对象本身,return *this;

#include 

using namespace std;

class Person{
public:
	Person(int age){
		// 使用this指针解决命名冲突
		this->age=age;
	}
	// 返回对象本身时需要使用引用类型
	// 如果返回的不是引用,那么外层调用位置在赋值时就会调用拷贝构造生成新的临时对象
	Person& PersonAddAge(Person& p) {
		this->age += p.age;
		return *this;
	}
	int age;
};

void test(){
	Person p(18);
	cout << p.age << endl;
}

void test1(){
	Person p1(10);
	Person p2(10);
	// 链式编程思想
	p1.PersonAddAge(p2).PersonAddAge(p2).PersonAddAge(p2).PersonAddAge(p2);
	cout << p1.age << endl;
}

int main(){
	test();
	test1();
	return 0;
}

空指针访问成员函数

  • 空指针是可以正常的访问成员函数的
#include 

using namespace std;

class Person{
public:
        void showClassName() {
                cout << "this is Person class" << endl;
        }

        void showPersonAge() {
                // 增加代码的健壮性
                ifthis == NULL{
                        return;
                }
                // 默认类中的成员属性都会有一个默认的this指针,即this->age
                cout << age << endl;
        }
        int age;
};

void test(){
        Person * p = NULL;
        p->showClassName();
        // p->showPersonAge() 空指针奔溃,因为内部使用的是this->age,而this是一个NULL
        p->showPersonAge();
}

int main(){
        test();
        return 0;
}

const修饰成员函数

常函数

  • 成员函数后加上const之后,我们称这个函数为常函数

  • 常函数内不可以修改成员属性

  • 成员属性声明时加上mutable后,在常函数中依然可以修改

常对象

  • 声明对象前加const称该对象为常对象

  • 常对象只能调用常函数

#include 

using namespace std;

class Person{
public:
	Person(){}
	// this指针相当于Person* const this,this指向的对象不能更改
	// 在函数后面加上const相当于 const Person* const this,this指向的对象和值都不能改
	void showPerson() const {
		// age = 10;
		// a有mutable修饰所以可以更改
		a = 1;
	}
	void func(){}
	int age;
	mutable int a;
};

// 常函数测试
void test(){
	Person p;
	p.showPerson();
}

// 常对象测试
void test1(){
	// 在对象前加const,变为常对象
        const Person p;
        // p.age =1;
	// a是mutable修饰的变量,在常对象下也可以修改
	p.a = 1;

	// 常对象只能调用常函数
	p.showPerson();
	// 常对象不能调用普通函数,因为在常对象限制了只能修改mutable修饰的变量,而在普通函数中可以修改任意变量
	// p.func();
}

int main(){
	test();
	test1();
	return 0;
}

友元

  • 友元的目的就是让一个类或者函数访问另一个类中的私有成员

使用全局函数做友元

#include 
#include 

using namespace std;

class Building{
	// 声明GoodGay为Building的友元函数,这样在GoodGay函数中可以访问Building的私有成员变量
	friend void GoodGay(Building& building);
public:
	Building(){
		sittingRoom = "sittingRoom";
		bedingRoom = "bedingRoom";
	}
public:
	string sittingRoom;
private:
	string bedingRoom;
};

// 全局函数想访问类的私有成员
void GoodGay(Building& building){
	cout << "Good Gay 正在访问:" << building.sittingRoom << endl;
	cout << "Good Gay 正在访问:" << building.bedingRoom << endl;
}

void test(){
	Building b;
	GoodGay(b);
}

int main(){
	test();
	return 0;
}

类做友元

  • 让友元类可以访问另一个类中的所有成员函数
#include 
#include 
using namespace std;

class Building;
class GoodGay {
public:
	GoodGay();
	// visit()函数访问Building中的属性
	void visit();
	Building* b;
};

class Building{
	// 声明GoodGay类是Building的友元类
	friend class GoodGay;
public:
	Building();
public:
	string sittingRoom;
private:
	string bedingRoom;
};

// 类外实现成员函数
GoodGay::GoodGay(){
	// 构造函数中创建一个Building的对象
	b=new Building;
}
void GoodGay::visit(){
	cout << "GoodGay class正在访问: " << b->sittingRoom << endl;
	cout << "GoodGay class正在访问: " << b->bedingRoom << endl;
}

Building::Building(){
	sittingRoom="sittingRoom";
    bedingRoom="bedingRoom";
}
// 测试
void test(){
	GoodGay gg;
	gg.visit();
}

int main(){
	test();
	return 0;
}

成员函数做友元

  • 让类中的某些成员函数可以访问另一个类中的私有成员变量
#include 
#include 
using namespace std;

class Building;
class GoodGay {
public:
	GoodGay();
	// visit()函数访问Building中的私有属性
	void visit();
	// visit1()函数不可以访问Building中的私有属性
	void visit1();
	Building* b;
};

class Building{
	// 声明GoodGay类下的visit函数作为Building类的友元函数
	friend void GoodGay::visit();
public:
	Building();
public:
	string sittingRoom;
private:
	string bedingRoom;
};

// 类外实现成员函数
GoodGay::GoodGay(){
	// 构造函数中创建一个Building的对象
	b=new Building;
}
void GoodGay::visit(){
	cout << "GoodGay visit正在访问: " << b->sittingRoom << endl;
	cout << "GoodGay visit正在访问: " << b->bedingRoom << endl;
}

void GoodGay::visit1(){
        cout << "GoodGay visit1正在访问: " << b->sittingRoom << endl;
}

Building::Building(){
	sittingRoom="sittingRoom";
        bedingRoom="bedingRoom";
}

void test(){
	GoodGay gg;
	gg.visit();
	gg.visit1();
}

int main(){
	test();
	return 0;
}

你可能感兴趣的:(#,C++笔记,c++,开发语言)