• C++笔记 04


    C++面向对象的三大特性为:封装、继承、多态
    C++认为万事万物都皆为对象,对象上有其属性和行为。

    例如:
    人可以作为对象,属性有姓名、年龄、身高、体重等,行为有走、跑、跳、吃饭、唱歌等;
    车可以作为对象,属性有轮胎、方向盘、车灯等,行为有载人、放音乐、开空调等。
    具有相同性质的对象,我们可以抽象为类,人属于人类,车属于车类。

    一. 封装

    封装是C++面向对象三大特性之一

    封装的意义:
    1)将属性和行为作为一个整体,表现生活中的事务;
    2)将属性和行为加以权限控制

    语法:class 类名 { 访问权限: 属性/行为 };

    //设计一个圆类,求圆的周长
    //class 代表设计一个类,类后面紧跟着的就是类名称
    const double PI=3.14;
    
    class Circle 
    {
    	//访问权限,公共权限
    	public: 
    			//属性(通常是一些变量)
    			//半径
    			int m_r;
    			
    			//行为(通常是一些函数)
    			//获取圆的周长
    			double calculateZC()
    			{
    				return 2*PI*m_r;
    			}
    };
    
    int main()
    {
    	//通过圆类,创建具体的圆(对象)
    	//实例化(通过一个类,创建一个对象的过程)
    	Circle cl;
    
    	//给圆对象的属性进行赋值
    	cl.m_r=10;
    	cout<<"圆的周长为"<<cl.calculateZC()<<endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    类中的属性和行为,统一称为成员;
    属性也称为成员属性成员变量行为也称为成员函数成员方法

    访问权限有三种:
    public: 公共权限 (类内可以访问,类外可以访问)
    protected: 保护权限 (类内可以访问,类外不可以访问)
    private: 私有权限 (类内可以访问,类外不可以访问)

    建议将成员属性设置为私有
    优点1:将所有成员属性设置为私有,可以自己控制读写权限
    优点2:对于写权限,可以检测数据的有效性

    二. 对象的初始化和清理

    • 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除一些自己信息数据保证安全。
    • C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置。

    1. 构造函数和析构函数

    对象的初始化和清理也是两个非常重要的安全问题,一个对象或者变量没有初始状态,对其使用后果是未知的,同样的使用完一个对象或者变量,没有及时清理,也会造成一定的安全问题。

    C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要求我们做的事情,因此如果我们不提供构造和析构,编译器会提供,编译器提供的构造函数和析构函数是空实现。

    构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
    析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

    构造函数语法类名 () { }
    1)构造函数,没有返回值也不写void ;
    2) 函数名称与类名相同;
    3) 构造函数可以有参数,可以发生重载;
    4)程序在调用对象的时候会自动调用构造,无需手动调用,而且只会调用一次

    析构函数语法~类名 () { }
    1)析构函数,没有返回值也不写void;
    2)函数名称与类名相同,在名称前加上符号~;
    3)析构函数不可以有参数,因此不可以发生重载;
    4)程序在调用对象的时候会自动调用析构,无需手动调用,而且只会调用一次。

    class Person
    {
    	public: 
    		//构造函数
    		Person()
    		{
    			cout<<"Person的构造函数调用"<<endl;
    		}
    
    		//析构函数
    		~Person()
    		{
    			cout<<"Person的析构函数调用"<<endl;
    		}
    };
    
    void test01()
    {
    	Person p;
    }
    
    int main()
    {
    	test01();  /*此时输出为“Person的构造函数调用”“Person的析构函数调用”(二者均有)
    						因为p在栈区,test01()执行完后,p就会被释放*/
    	system("pause");
    	return 0;
    }
    
    //若主函数改为:
    int main()
    {
    	Person p;       /*此时输出只有“Person的构造函数调用”,程序停在system("pause")处,
    	未执行完,所以不会调用析构函数*/
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

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

    两种分类方式:
    按参数分为:有参构造无参构造(又称为默认构造函数)
    按类型分为:普通构造拷贝构造

    三种调用方式:
    括号法;显示法;隐式转换法

    class Person
    {
    	public:
    		//无参(默认)构造函数
    		Person()
    			{
    				cout<<"调用无参构造函数"<<endl;
    			}
    			
    		//有参构造函数
    		Person(int a)
    		{
    			age=a;
    			cout<<"调用有参构造函数"<<endl;
    		}
    		
    		//拷贝构造函数
    		Person(const &p)
    		{
    			age=p.age;
    			cout<<"调用拷贝构造函数"<<endl;
    		}
    		
    		//析构函数
    		~Person()
    		{
    			cout<<"调用析构函数"<<endl;
    		}
    	public:
    		int age;
    };
    
    //调用无参构造函数
    void test01()
    {
    	Person p; //调用无参构造函数
    }
    
    //调用有参构造函数
    void test02()
    {
    	//括号法,常用
    	Person p1(10); /*注意,调用无参构造函数不能加括号,如Person p1();
    						如果加了,编译器认为这是一个函数声明*/
    	Person p2(p1); //调用拷贝构造
    
    	//显示法
    	Person p2=Person(10);
    	Person p3=Person(p2);
    	/*Person(10)单独写就是匿名对象,当前行结束之后,马上析构*/
    
    	//隐式转换法
    	Person p4=10; //相当于Person p4=Person(10);
    	Person p5=p4;//相当于Person p5=Person(p4); 
    	/*注意,不要利用拷贝构造函数初始化匿名对象,如Person(p3);
    		编译器会认为Person(p3)等价于Person p3,从而产生重定义问题*/
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    3. 拷贝构造函数的调用时机

    C++拷贝构造函数调用时机通常有三种情况:
    1)使用一个已经创建完毕的对象来初始化一个新对象
    2)值传递的方式给函数参数传值
    3)以值方式返回局部对象

    #include<iostream>
    using namespace std;
    
    class Person 
    {
    public: 
    	int mAge;
    
    public:
    	Person() 
    	{
    		cout << "无参构造函数" << endl;
    	}
    
    	Person(int age)
    	{
    		cout << "有参构造函数" << endl;
    		this->mAge = age;
    	}
    
    	Person(const Person& p)
    	{
    		cout << "拷贝构造函数" << endl;
    		this->mAge = p.mAge;
    	}
    
    	//析构函数在释放内存之前调用
    	~Person()
    	{
    		cout << "析构函数调用" << endl;
    	}
    };
    
    //1.使用一个已经创建完毕的对象初始化另一个对象
    void test01()
    {
    	Person man(100);  //调用有参构造函数
    	Person newman(man); //调用拷贝构造函数
    	Person newman2 = man; //调用拷贝构造函数
    	Person newman3;  
    	newman3 = man;//此处不是调用拷贝构造函数,而是赋值操作。
    	              
    /*
     拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用,注意二者的区别
    */
    }
    
    //2.一个对象以值传递的方式给函数参数传值
    void dowork(Person p)
    {
    	p.mAge = 1;
    	cout << p.mAge << endl;
    }
    void test02()
    {
    	Person p; //无参构造函数
    	p.mAge = 2;
    	dowork(p);  /*值传递,实参传给形参时,会调用拷贝构造函数,形参中的p会按照实参中的p拷贝一个副本,
    				在函数dowork(Person p1)中做赋值操作,不会影响dowork(p)中的p值
    				*/
    	cout << p.mAge << endl; 
    }
    
    //3.一个对象以值方式从函数返回(以值方式返回局部对象)
    Person dowork2()
    {
    	Person p1;//局部对象
    	cout << (int*)&p1 << endl;//用于验证地址是否相同
    	return p1;               //会拷贝一个p1返回,调用拷贝构造函数
    }
    void test03()
    {
    	Person p = dowork2();
    	cout << (int*)&p << endl; 
    }
    int main()
    {
    	test03();
    	system("pause");
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    4. 构造函数调用规则

    默认情况下,C++编译器至少给一个类添加3个函数
    1)默认构造函数(无参,函数体为空)
    2)默认析构函数(无参,函数体为空)
    3)默认拷贝构造函数,对属性进行值拷贝

    构造函数调用规则如下:

    • 如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
    • 如果用户定义拷贝构造函数,C++不再提供其他构造函数

    5. 深拷贝与浅拷贝

    浅拷贝:简单的赋值拷贝操作(默认的拷贝构造函数就是浅拷贝)
    深拷贝:在堆区重新申请空间,进行拷贝操作

    示例:

    class Person
    {
    	public:
    		//无参(默认)构造函数
    		Person()
    		{
    			cout<<"调用无参构造函数"<<endl;
    		}
    
    		//有参构造函数
    		Person(int age,int height)
    		{
    			cout<<"调用有参构造函数"<<endl;
    			m_age=age;
    			m_height=new int(height);
    		}
    
    		//拷贝构造函数
    		Person(const Person &p)
    		{
    			cout<<"调用拷贝构造函数"<<endl;
    			/*如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题*/
    
    			m_age=p.m_age;
    			m_height=new int(*p.m_height); //深拷贝操作
    			//m_height=p.m_height; 编译器默认是实现这行代码
    		}
    
    		//析构函数
    		~Person()
    		{
    			//析构代码,将堆区开辟数据做释放操作
    			cout<<"调用析构函数"<<endl;
    			if(m_height!=NULL)
    			{
    			delete m_height; //释放指针指向的内存
    			m_height=NULL; //置空,防止野指针出现
    			}
            }
        public:
        	int m_age;
        	int *m_height;
    };
    
    void test01()
    {
    	Person p1(18,180);
    	Person p2(p1);
        
        cout<<"p1的年龄:"<<p1.m_age<<"身高:"<<*p1.m_height<<endl;  // *p1.m_height 解引用
        cout<<"p2的年龄:"<<p2.m_age<<"身高:"<<*p2.m_height<<endl;
    }
    
    int main()
    {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题;
    当类中数据成员没有指针时,利用浅拷贝完全没问题;当成员中有指针时,要使用深拷贝
    在这里插入图片描述

    6. 初始化列表

    作用:C++提供了初始化列表语法,用来初始化属性
    语法:构造函数():属性1(值1),属性2(值2),....{ }

    //传统初始化操作
    public:
    	Person(int a,int b, int c)
    	{
    		m_A=a;
    		m_B=b;
    		m_C=c;
    	}
    	int m_A;
    	int m_B;
    	int m_C;
    
    //初始化列表初始化属性
    Person (int a,int b,int c):m_A(a),m_B(b),m_C(c) { }
    
    int main()
    {
    	Person p(30,20,10);
    	......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    7. 类对象作为类成员

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

    当其他类对象作为本类成员,构造时候先构造类对象(其他),再构造自身(本);析构的顺序与构造相反。

    8. 静态成员

    静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员。

    静态成员分为:

    静态成员变量
    1)所有对象共享同一份数据;
    2)在编译阶段分配内存
    3)类内声明,类外初始化
    静态成员函数
    1)所有对象共享同一个函数;
    2)静态成员函数只能访问静态成员变量;

    class Person
    {
    	public:
    		static int m_A; //类内声明
    	private:
    		static int m_B; //静态成员变量也是有访问权限的
    };
    int Person::m_A=10; //类外初始化
    int Person::m_B=10;
    
    void test01()
    {
    	//静态成员变量两种访问方式
    	//1.通过对象
    	Person p1;
    	p1.m_A=100;
    	cout<<"p1.m_A="<<p1.m_A<<endl;  //输出为p1.m_A=100
    
    	Person p2;
    	p2.m_A=200;
    	cout<<"p1.m_A="<<p1.m_A<<endl; //输出为p1.m_A=200
    	cout<<"p2.m_A="<<p2.m_A<<endl; //输出为p2.m_A=200
    	/* 所有对象都共享同一份数据 */
    
    	//2. 通过类名
    	cout<<"m_A="<<Person::m_A<<endl;
    	// cout<<"m_B="<<Person::m_B<<endl; 私有权限访问不到,类外访问不到私有静态成员变量
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    /*静态成员函数不可以访问非静态成员变量,因为无法区分到底是哪个对象的成员变量*/
    
    public:
    	static int m_A; //静态成员变量
    	int m_B; //非静态成员变量
    	static void func()
    	{
    		m_A=100;
    		//m_B=200; 静态成员函数不可以访问非静态成员变量
    		cout<<"static void func()调用"<<endl;
    		
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  • 相关阅读:
    openpnp - 接入西门子二手飞达
    express + log4js记录日志,包含每个请求的响应时间、请求参数和返回数据
    <%=%>模板写法
    IDEA自定义Maven仓库
    2023年华数杯数学建模A题隔热材料的结构优化控制研究解题全过程文档及程序
    ReferenceError: primordials is not defined错误解决
    Netty——ByteBuffer消息粘包、半包示例
    自定义通知栏显示不全、heads-up通知栏的应用
    基于SSM的培训学校教学管理平台的设计与实现
    Leetcode 37. 解数独
  • 原文地址:https://blog.csdn.net/pangyushuang/article/details/125496821