c++第六天(封装和构造函数,拷贝构造(深拷贝))

22 篇文章 2 订阅

目录

一、为什么要封装?

二、c++ 构造函数

三、拷贝构造函数

四、c++形参列表

五、析构函数


一、为什么要封装?

提高代码的稳定性。

以下代码可以看出,c++不同的编译器会有不同,有些编译器在数组越界时也不会报错和警告,直接打印数据,并且正确。我使用两小时测试了c++数组对字符串的界定。

例如如下代码:

#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
int main()
{
    char arr[5]={0};
    strcpy(arr,"helloworld    ");
    strcat(arr,"goodgoodgood");
    cout <<  arr << endl;
}

可以看出数组已越界,在c语言里面应该是打印不了的,会直接段错误,我们看看c++的处理;

它直接执行到最后的语句并打印了正确结果,有时候它对小于自己的字符串1可以接上,有时候又会段错误,飘忽不定的。因此需要封装一下。下面实现两个字符串的连接。

#include <iostream>
#include <string.h>
using namespace std;

//因为c++编译器问题,数组越界也能正常打印,所以需要封装
//以下代码可实现两个字符串计算并打印,实现c++封装特性
//实现类似strcat的代码
class str
{
public:
    char *cat(char *str1, char *str2)
    {

        bzero(buf, sizeof(buf));         //清除上次缓冲区的数据
        j = strlen(str1) + strlen(str2); //计算两个字符串长度

        //如果两个字符串相加小于或等于缓冲区时,进行下面程序
        if (j <= sizeof(buf))
        {   
            strcat(buf, str1); //直接拷贝
            strcat(buf, str2); //直接追加
            return buf;        //返回值
        }
        //如果第一个字符串>=缓冲区,或第二个字符串>=缓冲区大小时,进行下面程序
        else
        {  
            for (i = 0,k=0; i < sizeof(buf); i++) //缓冲区大小
            {                    
                buf[i] = str1[i]; //拷贝str1到buf中
                if (str1[i] == '\0')
                { //判断str1到结尾     
               
                    while (i<sizeof(buf))//进行第二个判断
                    {       
                        buf[i] = str2[k]; //拷贝str2到buf中
                        i++;  
                        k++;            // k++遍历str2
                    }
                    break;
                }
                
            }
            return buf; //返回strcat的值
        }
    }

private:
    char buf[10] = {0};
    int i = 0;
    int j = 0;
    int k = 0;
};

int main()
{
    str p;
    char *g = p.cat("hello", "world");
    cout << g << endl;
    
    char *g2 = p.cat("123", "456");
    cout << g2 << endl;
}

一个好的封装可以避免产生内存泄露和数据被修改的重大错误,用户只能在有限的范围内访问,不会触发内部核心代码。

二、c++ 构造函数

作用: 初始化类中的数据成员,让一个对象创建后就立即可以使用。

构造函数特点:

1.函数名与类名相同

2.函数没有返回值。

3.函数不需要用户调用,在创建对象的时候自动调用。

4.分为有参构造和无参构造。

5.构造函数可以重载。

#include<iostream>
#include<string.h>
using namespace std;

//在构造函数初始化,在外部方法(函数)中调用这些对象(已初始化的变量)
class student{
public:
    //无参构造函数
    student(){
        strcpy(name,"小方");
        id = 1008611;
        age = 20;
    }
    void show(){    //调用无参构造函数的对象
        cout << "无参构造:" << endl;
        cout << "姓名  " << "年龄 " << "id"<< endl;  
        cout << name << "  " << age << "   " << id << endl ;
    }

    //有参构造函数
    student(char *name,int age,int id){
        strcpy(this->name,name);  
        this->age = age;    //this指针用法:左边this->定义的对象 = 传参进来的对象
        this->id  = id;     //this指针作用:可以在该构造函数使用同名对象,在main函数定义一个变量即可
    }
    void show2(){   //调用有参构造函数的对象
        cout << "有参构造:\n";
        cout << "姓名  " << "年龄 " << "id"<< endl;
        cout << name <<"  "<< age <<"   "<< id<< endl; //这里面加不加this->都可以
    }
private:
    char name[10];
    int id;
    int age;
};

int main()
{
    student s; 
    s.show();  //无参调用

    student k("小红",21,1008612);   //有参调用
    k.show2();
    
}

!this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给 this。不过 this 这个形参是隐式的,它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。this 作为隐式形参,本质上是成员函数的局部变量,所以只能用在成员函数的内部,并且只有在通过对象调用成员函数时才给 this 赋值。

每一个类的内部,都有一个this 指针,指向当前类的首地址!作用: 区分类内成员与类外成员

(如果函数内对象不重名的话不用this指针也行,因为它的作用是使重名的变量区分开来)。

三、拷贝构造函数

1、当一个类定义时通过另一个类进行初始化,就会调用拷贝构造函数。 (系统自动生成的拷贝构造函数 : 浅拷贝)。

xbase c = b; //发生了隐式转换,在定义的时候初始化。

c.xb_show();

浅拷贝: 类中的数据成员全部都是单纯的值,用户直接调用系统自动生成的浅拷贝构造函数即可。

#include<iostream>
using namespace std;

class xbase{
public:
    xbase(){cout << "无参构造函数\n";}
    xbase(int data,int data2){
        cout << "有参构造函数" << endl;
        this->data2 = data2;
        this->data = data;
    }
    xbase(const xbase &tmp){    //自定义拷贝构造函数(浅拷贝)
        this->data = tmp.data;
        this->data2 = tmp.data2;
    }
    void xb_show(){
        cout << this->data << endl;
        cout << this->data2 << endl;
    }
private:
    int data;
    int data2;
};
int main()
{
    xbase a(10,20);    //普通的有参调用
    a.xb_show();

    xbase b = a;   //构造函数隐式转换,相当于base b(a);
    cout << "拷贝构造函数." << endl;
    b.xb_show();

    xbase c = b;    //系统自动生成拷贝构造函数(浅拷贝)!!发生了隐式转换
    cout << "拷贝构造函数." << endl;
    c.xb_show();    //在定义的时候初始化,没有调用有参构造函数

    xbase d;        //先定义,调用了无参构造。
    d = a;          //再调用赋值运算符,与拷贝函数无关
    d.xb_show();    

}

深拷贝: 当一个类中分配了 堆空间资源时,浅拷贝构造函数会造成,多个类共同指向一块堆空间。 造成资源的抢占或者重复释放。

#include<iostream>
#include<string.h>
using namespace std;
//由于浅拷贝的系统构造函数没有分配堆空间,因此需要深拷贝给拷贝函数分配空间
static int i=2;
class xbase{
public:
    xbase(){cout << "我是无参构造函数\n";}
    xbase(int data,int data2,const char *s){
        cout << "有参构造函数" << endl;
        this->data2 = data2;
        this->data = data;
        str = new char[1024];
        strcpy(this->str,s);
        cout << "申请str1地址:" << &(this->str) << endl;
    }
    xbase(const xbase &tmp){    //自定义拷贝构造函数(深拷贝)
        //寻找格式:tmp.定义的变量
        //基本是复制构造函数,只不过要调用一个引用参数
        this->data = tmp.data;
        this->data2 = tmp.data2;
        this->str = new char[1024];
        strcpy(this->str,tmp.str);
       
        cout << "申请str2地址:" << &(this->str) << endl;
    }
    ~xbase(){
        
        delete []str;
        cout << "释放str" <<i--<<"空间:"<< &(this->str) << endl;
    }
    void xb_show(){
        cout << this->data << endl;
        cout << this->data2 << endl;
    }
private:
    int data;
    int data2;
    char *str;
};

int main()
{
    xbase s(10,20,"hello");

    xbase p = s;    //深拷贝,参数是base &tmp。引用格式:类 &参数

}

1、new和malloc在构造函数中的区别(最根本的不同)

#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
// new和malloc的区别在于malloc不能调用构造函数,所有无法给类对象分配空间
class demo{
public:
    demo(){          // 无参构造
        cout << "我是无参构造函数!\n";
    }
    demo(int b){    //有参构造
        data = b;
        cout << "我是有参构造函数!  data:" << data << endl;
    }
private:
    int data;
};

int main()
{
   // new和malloc的区别
    demo *p = (demo *)malloc(sizeof(demo)); //用malloc不能调用构造函数,也就不能使用类对象

    demo *q = new demo;  //默认调用无参构造函数

    demo *q2 = new demo(10086);  //调用有参构造函数

}

结果:

总结:因为malloc 无法调用类中的构造函数,导致类中的数据成员无法初始化!  new是c++新创的运算符,可以申请堆空间,也可以初始化。数据类型 *变量名 = new 数据类型[大小];delete()释放。

四、c++形参列表

     参数列表初始化的方式有三种:第一种是在定义的时候直接初始化,第二种是用一个函数来初始化,第三种是用构造函数初始化。

    *构造函数初始化的方式:

1、在函数内赋值部初始化,使用this指针(不重名就不用)。

2、使用参数列表初始化:

    //参数列表初始化的方式,函数(参数,参数..):类内定义的对象(参数),类内定义的对象(参数)...

    //参数列表初始化不能初始化char*型的数据

    //()里面的是形参,外面的是成员变量,将()里面的形参赋值给外面的成员初始化。

#include<iostream>
#include<string.h>
using namespace std;

class student{
public:
    student(){cout << "无参构造函数\n";}
    //参数列表初始化的方式,函数(参数,参数..):类内定义的对象(参数),类内定义的对象(参数)...
    //参数列表初始化不能初始化char*型的数据
    //()里面的是形参,外面的是成员变量,将()里面的形参赋值给外面的成员初始化。
    student(int age,int tall,const char *n):age(age),tall(tall){
        strcpy(name,n);
    }
    void stu_show(){
        cout << "姓名:" <<  name << "  身高:"<< tall << "  年龄:" << age << endl; 
    }
private:
    int age;
    int tall;
    char name[10]={0};
};

int main()
{
    student p(23,188,"小强"); //有参构造并声明一个类对象
    p.stu_show();
}

五、析构函数

作用:在类对象消亡时释放该类内存,(例如:分配的动态内存空间,打开的文件,映射的空间...) 。

特点:1.、函数名与类名相同在前面加~

2、函数没有返回值,没有参数,因此不可以重载。

3、当对象销毁时候系统自动调用。(不用自己调用)

4、用new申请的类对象需要自己手动释放才能调用析构函数。(因为自由储存区的空间不能用函数周期来结束,因为函数存在栈区中)。

5、先建立的对象先用拷贝构造函数,后建立的后用,调用析构函数优先,当删除对象时会自动调用析构函数,先删除后建立的,再删除先调用的。(和栈一样,先进后出)。

下面用打开文件的例子来说明一下析构函数的用法:

#include<iostream>
extern "C"{
#include<stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
}
using namespace std;

class File{
public:
    // File(){}
    File(const char *f="hello.txt"){
        fd = open(f,O_RDWR|O_CREAT);
        if(fd<0){
            
            cout << fd <<endl;
        }
        else{
            printf("打开文件成功 %d\n",fd);
        }
        str = new char[1024];
        cout << "申请动态内存\n" ;
    }
    ~File(){
        close(fd);
        delete []str;
        str = NULL;
        cout << "已释放动态内存str:" << str << endl;
    }
private:
    int fd;
    char * str;
};

//等待所有构造函数执行完才调用析构函数
void test(){

    File f;                //默认打开hello.txt,对象消亡,自动调用析构函数
    File f1("good.txt");   //对象消亡,自动调用析构函数
    File f2("tes.txt");    
    File f3("tes2.txt");   
    
    File *f4 = new File;
    delete f4;  
    cout << "该函数周期已结束,开始调用析构函数:\n";
}

int main()
{
    int i=10;
    while (i--)
    {
        test();
    }
    
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值