c++类和动态内存分配

这篇博客探讨了C++编程中类的声明、方法定义、构造和析构函数的使用,特别是如何进行动态内存分配。通过学习,读者可以掌握在C++中有效地管理对象生命周期和内存的方法。

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》






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值