c++类和动态内存分配

来源:互联网 发布:win10usb端口上的电涌 编辑:程序博客网 时间:2024/06/04 19:11

1、类声明

C++中使用关键字 class 来声明类, 其基本形式如下:
class 类名
{
public:
//行为或属性 
private:
//行为或属性
};
类是一种将抽象转换为用户定义类型的c++工具,它将数据表示和操作数据的方法整合为一个包。在声明部分,c++以数据成员的方式描述数据部分,以成员函数的方式描述共有接口,下面看一个例子:我们要定义一个类型,储存多幅图像的数据(rgb值)以及图像的数量、通道、长宽等信息。

#pragma once#include <vector>#include <memory>#include <iostream>#include <string.h>class Blob{public:Blob();Blob(int n, int c, int h, int w);~Blob();int num;int channels;int height;int width;int data_count;private:std::shared_ptr<float> data;};
访问控制:private和public关键字用于访问控制,使用类对象的程序都可以访问共有成员数据和函数,但是只能通过公有成员函数来访问私有成员。这里我们将数据放在共有部分为了方便直接访问,正常出于隐藏数据的主要目标,数据项一般定义在私有部分,而通过成员函数去操作数据。
同时注意到,在声明部分,我们只规定函数接受什么类型的数据,而没有具体的实现。

2、类方法定义

类方法定义主要描述了如何实现类成员函数。定义成员函数时,用作用域解析运算符(::)来标识函数所属的类。
#include "Blob.h"Blob::Blob(){data = nullptr;}Blob::Blob(int n, int c, int h, int w){if (data)data = nullptr;num = n;channels = c;height = h;width = w;data_count = n * c * h * w;data.reset(new float[data_count], std::default_delete<float[]>());}Blob::~Blob(){}
以上,我们就完成了对一个简单类型的定义,调用方法和结构类似。

3、类的构造和析构函数

3.1构造函数

构造函数用于类成员的初始化,其名称都和类名相同。在上面的例子中,
Blob();
Blob(int n, int c, int h, int w);
都是构造函数。之前也讲到,出于数据隐藏的目的,数据部分一般放在私有访问中,也就是说我们无法通过直接对变量赋值的方法来初始化一个类,这就用到了构造函数。
我们注意到第一个构造函数没有接受任何参数。这就是默认构造函数,就像是我们用 int i; 去定义一个整数那样,不提供初始值。如果没有提供任何构造函数,将由c++自动提供,不做任何操作。

3.1.1 构造函数使用

两种方法:Blob image(1,2,3,4);
    Blob image_=Blob(1,2,3,4);

3.2析构函数

用构造函数创建对象后,程序会跟踪对象,直到过期为止。对象过期时,程序将调用析构函数,用于清理工作。例如,如果在构造函数中用new分配了一个内存,则析构函数将使用delete来释放内存。若析构函数不需要进行操作,则编写为空的函数。
析构函数的原型:~Blob(); 实现:Blob::~Blob()  }

注意:构造函数不能想普通函数一样调用。若上例中还有一个函数Blob::copy()则可以通过image.copy()来调用,而构造函数则不能。

4、动态内存分配

看下面这个声明和定义:
class String{private:char* str;int len;static int str_num;public:String();String(const char*s);~String();};
String::String(){len=4;str=new char[4];std::strcpy(str,"c++");str_num++;cout<<str_num<<"objects left";}String::String(const char* s){len=std::strlen(s);str=new char[len+1];std::strcpy(str,s);str_num++;cout<<str_num<<"objects left";}String::~String(){--str_num;cout<<str_num<<"objects left";}
功能是储存字符串,用一个str_num来计数。这里str_num为静态数据成员,也就是说对于所有成员,只创建一个共享的副本。
其中strcpy()的作用是把字符串复制到新的内存中。
因为在构造函数中使用了new,所以在析构函数中必须调用delete来释放内存。这也是我们下面讨论问题的关键。
String str1("aaa");String str2("bbb");String str3("ccc");pass1(str1);cout<<"str1:"<<str1<<endl;pass2(str2);cout<<"str2:"<<str2<<endl;void pass1(String & rsb){cout<<"  "<<rsb;}void pass2(String sb){cout<<"  "<<sb;}
以上是一段调用了上面String定义的代码,其中pass1函数表示参数按引用传递,pass2表示参数按值传递。
这一段的运行结果:pass1的部分,输出正常,而pass2的部分,产生了不可预期的输出。为什么呢?
str2作为函数参数被传递导致了析构函数被调用。虽然值传递可以防止原参数被修改,但是原字符串已被清理,无法识别。这同时也会导致程序退出后计数变量str_num的值为-1,显然是不正确的。这表明析构函数比构造函数多调用了一次,在值传递的时候。
还有一些不正确的使用会导致类似的错误,主要指一些特殊成员函数,我下周再做总结。

参考文献:《c++ prime plus》






原创粉丝点击