C++面向对象之“类“

C++面向对象之"类"

上一节讲了头文件。头文件
本节正式进入面向对象的编程,类。

  • 面向过程: 根据程序执行的先后顺序,来设计所有细节
    缺点:开发大型项目时,会导致难把控所有的细节。
  • 面向对象: 一种全新的开发方式。

类的基础

  • 最重要的一个概念:“类” class
    类是一种特殊的“数据结构",不是一个具体的数据。
    和基本的数据类型不同 (char/int/float)
    在实现类的时候一般把类与类的方法声明放到.h文件中,它的实现放在一个.cpp文件中。

  • 使用class 类名{
    public:
    成员或方法
    private:
    成员或方法
    };

  • 类的设计:
    在这里插入图片描述

  • 此处定义一个 "dog类"

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    //定义类 
    class Dog{
    public://公有的
    	void eat(); //吃 
    	void sleep(); //睡	
    	void play(); //玩 
    	
    	int getWeight(); //获取体重 
    	string getName();	
    		
    private://私有的 无法被内部直接访问 
    	 int Weight;//体重 
    	 string name; //名字 
    };
    
    //实现方法
    //成员函数前面要加Dog::表明该方法是类Dog的方法 
    void Dog::eat(){
    	cout << "吃肉" << endl; 
    }
    
    void Dog::sleep(){
    	cout << "睡觉" << endl; 
    }
    
    void Dog::play(){
    	cout << "玩" << endl; 
    }
    
    //通过两个内部public方法访问private成员 
    int Dog::getWeight(){
    	return Weight;
    }
    
    string Dog::getName(){
    	return name;
    }
    
    int main(){
        //定义一个类的对象
    	Dog dog;
        //失败,无法访问private成员
        //dog.name;
        //通过public方法访问私有成员
        dog.getName();
        
    	return 0;
    }
    

    此处定义一个Dog类,有类的public成员函数,有private成员。

  • 使用类中的成员需要类名.方法或者成员调用

  • 注意: private成员对外部无法访问,例如上面main函数中的dog.name失败!
    特点:安全,无法在外部被修改,只能通过内部方法修改。

  • 将private改成public可以让数据变得可以访问,但是不建议这么做,会降低安全性

类的默认构造函数

  • 在创建对象时初始化里面的数据成员就需要构造函数,不然就没有初始化。

  • 构造函数可以定义很多种构造函数

  • 在创建对象时,自动调用构造函数。

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    //定义类 
    class Dog{
    public://公有的
        //默认构造函数不定义会自动调用默认生成的
    	Dog();
        //自定义的构造函数
        Dog(const char* name, int weight);
        
    	void eat(); //吃 
    	void sleep(); //睡	
    	void play(); //玩 
    	
    	int getWeight(); //获取体重 
    	string getName();	
    		
    private://私有的 无法被内部直接访问 
    	 int Weight;//体重 
    	 string name; //名字 
    };
    
    //实现方法
    //默认构造函数
    Dog::Dog(){
        
    }
    
    Dog::Dog(const char* _name, int _weight){
    	this->name = _name;
    	this->Weight = _weight; 
    }
     
    //成员函数前面要加Dog::表明该方法是类Dog的方法 
    void Dog::eat(){
    	cout << "吃肉" << endl; 
    }
    
    void Dog::sleep(){
    	cout << "睡觉" << endl; 
    }
    
    void Dog::play(){
    	cout << "玩" << endl; 
    }
    
    //通过两个内部public方法访问private成员 
    int Dog::getWeight(){
    	return Weight;
    }
    
    string Dog::getName(){
    	return name;
    }
    
    int main(){
        //定义一个类的对象
    	Dog dog("旺财", 20);
        //失败,无法访问private成员
        //dog.name;
        //通过public方法访问私有成员
        dog.getName();
        
    	return 0;
    }
    

    this表示一个指针,代表了调用该方法的对象,比如Dog dog("旺财", 20);中对象dog调用构造函数Dog(const char* _name, int _weight)此时this->name即为对象本身的name成员。

  • 第一个构造函数Dog::Dog();为默认的构造函数,可以缺省,第二个Dog::Dog(const char* _name, int _weight)人工合成的默认构造函数。

类的拷贝构造函数

  • 拷贝构造函数就是相当于一种复制。自己不定义时,会自动生成默认的拷贝构造函数,具体的使用如下:

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    //定义类 
    class Dog{
    public://公有的
        //默认构造函数不定义会自动调用默认生成的
    	Dog();
        //自定义的构造函数
        Dog(const char* name, int weight);
        
    	void eat(); //吃 
    	void sleep(); //睡	
    	void play(); //玩 
    	
    	int getWeight(); //获取体重 
    	string getName();	
    		
    private://私有的 无法被内部直接访问 
    	 int Weight;//体重 
    	 string name; //名字 
    };
    
    //实现方法
    //默认构造函数
    Dog::Dog(){
        
    }
    
    Dog::Dog(const char* _name, int _weight){
    	this->name = _name;
    	this->Weight = _weight; 
    }
     
    //成员函数前面要加Dog::表明该方法是类Dog的方法 
    void Dog::eat(){
    	cout << "吃肉" << endl; 
    }
    
    void Dog::sleep(){
    	cout << "睡觉" << endl; 
    }
    
    void Dog::play(){
    	cout << "玩" << endl; 
    }
    
    //通过两个内部public方法访问private成员 
    int Dog::getWeight(){
    	return Weight;
    }
    
    string Dog::getName(){
    	return name;
    }
    
    int main(){
        //定义一个类的对象并调用自定义的默认构造函数
    	Dog dog("旺财", 20);
        //失败,无法访问private成员
        //dog.name;
        //通过public方法访问私有成员
        cout << "dog的名字是:" << dog.getName() << endl;
        
        //下面两种都会调用拷贝构造函数
        Dog dog1 = dog;
        Dog dog2(dog);
        
        cout << "dog1的名字是:" << dog.getName() << endl;
        cout << "dog2的名字是:" << dog.getName() << endl;
    	return 0;
    }
    

    输出结果:

    dog的名字是:旺财
    dog1的名字是:旺财
    dog2的名字是:旺财
    

    这里虽然没有定义拷贝构造函数,但是系统自动生成了拷贝构造函数并且调用。

    缺点: 默认拷贝函数只是一种"浅拷贝"而非"深拷贝"

    "深浅"拷贝

    为了让大家理解浅拷贝和深拷贝的区别,这里举个栗子:(暂时不主动释放new申请的空间)

    #include <iostream>
    #include <string.h>
    
    using namespace std;
    
    //为了方便,方法在类中实现
    class Test{
    public:
    	//默认构造函数
    	Test(const char* _str = "测试"){
        	//包含一个结束符
        	str = new char[strlen(_str) + 1];
        	//拷贝_str字符串到str中
        	strcpy_s(str, strlen(_str) + 1, _str);
    	}
    
    	//定义一个改变str成员的方法
    	void changeStr(const char* _str){
        	strcpy_s(str, strlen(_str) + 1, _str);
    	}
    
    	//定义一个获取str的方法
    	char* getStr(){
        	return str;
    	}
    
    private:
    	//字符串
    	char* str;
    };
    
    int main(){
    	//定义两个对象
    	Test t1("测试1");
    	//将t1拷贝到t2
    	Test t2(t1);
    
    	cout << "改变t2字符串前t1字符串为:" << t1.getStr() << endl;
    	//改变t2的字符串,并获取t1的字符串
    	t2.changeStr("测试2");
    	cout << "改变t2字符串后t1字符串为:" << t1.getStr() << endl;
    
    	return 0;
    }
    

    输出结果:

    改变t2字符串后t1字符串为:测试1
    改变t2字符串后t1字符串为:测试2
    

    此处由于"浅"拷贝的原因,改变t2导致t1跟着一起改变。具体解释就浅拷贝只是值的复制,所以拷贝后t2的str成员指向的内存空间和t1的str成员指向的内存空间是同一块内存空间,改变其中一个的值,另外一个会跟着变。
    所以使用系统默认的拷贝构造函数有致命的缺陷!

  • 使用 自定义的拷贝构造函数("深"拷贝) 来规避这种缺陷。

    #include <iostream>
    #include <string.h>
    
    using namespace std;
    
    //为了方便,方法在类中实现
    class Test{
    public:
    	//默认构造函数
    	Test(const char* _str = "测试"){
        	//包含一个结束符
        	str = new char[strlen(_str) + 1];
        	//拷贝_str字符串到str中
        	strcpy_s(str, strlen(_str) + 1, _str);
    	}
    
        //定义拷贝构造函数
        Test(const Test& other){
            if(other.str){
                this->str = new char[strlen(other.str) + 1];
                strcpy_s(this->str, strlen(other.str) + 1, other.str);
            }
        }
        
    	//定义一个改变str成员的方法
    	void changeStr(const char* _str){
        	strcpy_s(str, strlen(_str) + 1, _str);
    	}
    
    	//定义一个获取str的方法
    	char* getStr(){
        	return str;
    	}
    
    private:
    	//字符串
    	char* str;
    };
    
    int main(){
    	//定义两个对象
    	Test t1("测试1");
    	//将t1拷贝到t2
    	Test t2(t1);
    
    	cout << "改变t2字符串前t1字符串为:" << t1.getStr() << endl;
        cout << "改变t2字符串前t2字符串为:" << t2.getStr() << endl;
    	//改变t2的字符串,并获取t1的字符串
    	t2.changeStr("测试2");
    	cout << "改变t2字符串后t1字符串为:" << t1.getStr() << endl;
    	cout << "改变t2字符串后t2字符串为:" << t2.getStr() << endl;
    	return 0;
    }
    

    输出结果:

    改变t2字符串前t1字符串为:测试1
    改变t2字符串前t2字符串为:测试1
    改变t2字符串后t1字符串为:测试1
    改变t2字符串后t2字符串为:测试2
    

    这里成功采用深拷贝规避了浅拷贝带来的问题。

  • 注意赋值构造函数(特殊的拷贝构造函数)也是浅拷贝。

    #include <iostream>
    #include <string.h>
    
    using namespace std;
    
    //为了方便,方法在类中实现
    class Test{
    public:
    	//默认构造函数
    	Test(const char* _str = "测试"){
        	//包含一个结束符
        	str = new char[strlen(_str) + 1];
        	//拷贝_str字符串到str中
        	strcpy_s(str, strlen(_str) + 1, _str);
    	}
    
    	//定义一个改变str成员的方法
    	void changeStr(const char* _str){
        	strcpy_s(str, strlen(_str) + 1, _str);
    	}
    
    	//定义一个获取str的方法
    	char* getStr(){
        	return str;
    	}
    
    private:
    	//字符串
    	char* str;
    };
    
    int main(){
    	//定义两个对象
    	Test t1("测试1");
    	//将t1赋值给t2
    	Test t2 = t1;
    
    	cout << "改变t2字符串前t1字符串为:" << t1.getStr() << endl;
    	//改变t2的字符串,并获取t1的字符串
    	t2.changeStr("测试2");
    	cout << "改变t2字符串后t1字符串为:" << t1.getStr() << endl;
    
    	return 0;
    }
    

    输出结果:

    改变t2字符串后t1字符串为:测试1
    改变t2字符串后t1字符串为:测试2
    

    这里也同要是浅拷贝,一个好的类,应该也同时有赋值构造函数来避免浅拷贝(实质上为一个=运算符的重载)。

    #include <iostream>
    #include <string.h>
    
    using namespace std;
    
    //为了方便,方法在类中实现
    class Test{
    public:
    	//默认构造函数
    	Test(const char* _str = "测试"){
        	//包含一个结束符
        	str = new char[strlen(_str) + 1];
        	//拷贝_str字符串到str中
        	strcpy_s(str, strlen(_str) + 1, _str);
    	}
    
        //定义拷贝构造函数
        Test(const Test& other){
            this->str = new char[strlen(other.str) + 1];
            strcpy_s(this->str, strlen(other.str) + 1, other.str);
        }
        
        //定义赋值构造函数
    	Test& operator=(const Test& other){
    		if(this == &other)
    			return *this;
    		
    		if(other.str){
    			this->str = new char[strlen(other.str) + 1];
    		strcpy_s(this->str, strlen(other.str) + 1, other.str);
    		}
    		return *this;
    	}
        
    	//定义一个改变str成员的方法
    	void changeStr(const char* _str){
        	strcpy_s(str, strlen(_str) + 1, _str);
    	}
    
    	//定义一个获取str的方法
    	char* getStr(){
        	return str;
    	}
    
    private:
    	//字符串
    	char* str;
    };
    
    int main(){
    	//定义两个对象
    	Test t1("测试1");
    	//将t1拷贝到t2
    	Test t2 = t1;
    
    	cout << "改变t2字符串前t1字符串为:" << t1.getStr() << endl;
        cout << "改变t2字符串前t2字符串为:" << t2.getStr() << endl;
    	//改变t2的字符串,并获取t1的字符串
    	t2.changeStr("测试2");
    	cout << "改变t2字符串后t1字符串为:" << t1.getStr() << endl;
    	cout << "改变t2字符串后t2字符串为:" << t2.getStr() << endl;
    	return 0;
    }
    

    输出结果:

    改变t2字符串前t1字符串为:测试1
    改变t2字符串前t2字符串为:测试1
    改变t2字符串后t1字符串为:测试1
    改变t2字符串后t2字符串为:测试2
    

    这里重写了等号运算符,让其能够接受类型为Test的参数,等效于t2 =(t1);

    这里就把浅拷贝变成了深拷贝。避免了一些特殊情况。

类的析构函数

  • 析构函数和构造函数相对,用来删除类的函数。
    默认情况下也会自动生成。定义, 使用~类名()来定义。
    缺陷,它不会自动释放类的对象申请的内存空间。

    #include <iostream>
    #include <string.h>
    
    using namespace std;
    
    //为了方便,方法在类中实现
    class Test{
    public:
    	//默认构造函数
    	Test(const char* _str = "测试"){
        	//包含一个结束符
        	str = new char[strlen(_str) + 1];
        	//拷贝_str字符串到str中
        	strcpy_s(str, strlen(_str) + 1, _str);
    	}
    
        //定义拷贝构造函数
        Test(const Test& other){
            this->str = new char[strlen(other.str) + 1];
            strcpy_s(this->str, strlen(other.str) + 1, other.str);
        }
        
        //定义赋值构造函数
    	Test& operator=(const Test& other){
    		if(this == &other)
    			return *this;
    		
    		if(other.str){
    			this->str = new char[strlen(other.str) + 1];
    		strcpy_s(this->str, strlen(other.str) + 1, other.str);
    		}
    		return *this;
    	}
        
        //默认析构函数
        ~Test(){
            cout << str <<"调用析构函数" << endl;
        }
        
    	//定义一个改变str成员的方法
    	void changeStr(const char* _str){
        	strcpy_s(str, strlen(_str) + 1, _str);
    	}
    
    	//定义一个获取str的方法
    	char* getStr(){
        	return str;
    	}
    
    private:
    	//字符串
    	char* str;
    };
    
    int main(){
        //定义一个指针指向t2的str的内存空间
        char* point = NULL;
        
    	//定义两个对象
    	Test t1("测试1");
        
    	//t2在大括号结束,自动调用析构函数
        {
            Test t2("测试2");
            point = t2.getStr();
        }
        
        //访问t2.str的内存空间,此时
        cout << "point的值:" << point << endl;
        
    	return 0;
    }
    

    输出结果:

    测试2调用析构函数
    point的值:测试2
    测试1调用析构函数
    

    这里即使是t2被析构了,仍然能够访问t2.str的内存空间,**可见析构函数不会把对象中分配好的空间给释放掉。**这会导致非常严重的后果——内存泄漏!!!

  • 改进:改进析构函数,使其能够主动在对象的生命周期结束时释放掉已经分配的空间。
    这里为了测试,拷贝构造函数采用浅拷贝方式。

    #include <iostream>
    #include <string.h>
    
    using namespace std;
    
    //为了方便,方法在类中实现
    class Test {
    public:
        //默认构造函数
        Test(const char* _str) {
            //包含一个结束符
            str = new char[strlen(_str) + 1];
            //拷贝_str字符串到str中
            strcpy_s(str, strlen(_str) + 1, _str);
        }
    
        //默认析构函数
        ~Test() {
            if (str) delete[] str;
            cout << str << "调用析构函数" << endl;
            str = NULL;
        }
    
        //定义一个改变str成员的方法
        void changeStr(const char* _str) {
            strcpy_s(str, strlen(_str) + 1, _str);
        }
    
        //定义一个获取str的方法
        char* getStr() {
            return str;
        }
    
    private:
        //字符串
        char* str;
    };
    
    int main() {
    
        //定义两个对象
        Test t1;
        //t2在大括号结束,自动调用析构函数
        {
            Test t2("测试2");
            t1 = t2;
        }
    
        //访问t2.str的内存空间,此时
        cout << "t1.str的值:" << t1.getStr() << endl;
    
        return 0;
    }
    

    输出结果:一些奇怪的中午字符,乱码。所以这里成功使用析构函数释放掉了对象申请的空间。

本节介绍了类的基础,下节我们我们介绍类的函数成员。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值