您的位置:首页 > 编程语言 > C语言/C++

c++类和动态内存分配

2017-11-12 15:26 148 查看

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》

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: