C++-类和动态内存分配 大发彩_票平台开发

大发彩_票平台开发

地址一:【hubawl.com】狐霸源码论坛
地址二:【bbscherry.com】

类和动态内存分配

  1. 动态内存和类

C++在分配内存时是让程序在运行时决定内存分配,而不是在编译时决定。 这样,可根据程序的需要,而不是根据一系列严格的存储类型规则来使用内存。C++使用new和delete运算符来动态控制内存。

1.1. 复习示例和静态类成员

这个程序使用了一个新的存储类型:静态类成员。

//strngbad.h
#include<iostream>
#ifndef STRNGBADH
#define STRNGBADH
class StringBad
{
private:
char str; //指向字符串的指针
int len; //字符串的长度
static int num_strings; //对象个数
public:
StringBad(const char
s); //构造函数
StringBad(); //默认构造函数
~StringBad(); //析构函数
//友元函数
friend std::ostream & operator<<(std::ostream & os,
const StringBad & st);
};
#endif // !STRNGBADH
注意:

首先,它使用char指针(而不是char数组)来表示姓名。这意味着类声明没有为字符串本身分配存储空间,而是在构造函数中使用new来为字符串分配空间。 这避免了在类声明中预先定义字符串的长度。

其次,将num_strings成员声明为静态存储类。 静态类成员有一个特点:无论创建了多少对象,程序都只创建一个静态类变量副本。 也就是说,类的所有对象共享同一个静态成员。 假设创建了10个StringBad对象,将有10个str成员和10个len成员,但只有一个共享的num_strings成员。这对于所有类对象都具有相同值的类私有数据是非常方便的。 例如,num_strings成员可以记录所创建的对象数目。

//strngbad.cpp
#include<cstring>
#include"strngbad.h"
using std::cout;

//初始化静态类成员
int StringBad::num_strings = 0;

//类方法
//以C字符型构造StringBad
StringBad::StringBad(const char * s)
{
len = std::strlen(s); //设置长度
str = new char[len + 1]; //分配内存
std::strcpy(str, s); //初始化指针
num_strings++; //设置对象数量
cout << num_strings << ": \"" << str
<< "\" object created\n";
}
StringBad::StringBad() //默认构造函数
{
len = 4;
str = new char[4];
std::strcpy(str, "C++"); //默认字符串
num_strings++;
cout << num_strings << ": \"" << str << "\" default object created\n";
}
StringBad::~StringBad() //必要的析构函数
{
cout << "\"" << str << "\" object deleted, ";
--num_strings; //有要求
cout << num_strings << " left\n";
delete[] str; //有要求
}
std::ostream & operator<<(std::ostream & os, const StringBad & st)
{
os << st.str;
return os;
}
注意: 不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存。 对于静态类成员,可以在类声明之外使用单独的语句来进行初始化,这是因为静态类成员是单独存储的,而不是对象的组成部分。

析构函数:str成员指向new分配的内存。当StringBad对象过期时,str指针也将过期。 但str指向的内存仍被分配,除非使用delete将其释放。 删除对象可以释放对象本身占用的内存,但并不能自动释放属于对象成员的指针指向的内存。因此,必须使用析构函数。 在析构函数中使用delete语句可确保对象过期时,由构造函数使用new分配的内存被释放。

//vegnews.cpp
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using std::cout;
#include"strngbad.h"

void callme1(StringBad &); //传递引用
void callme2(StringBad); //按值传递

int main()
{
using std::endl;
{
cout << "Starting an inner block.\n";
StringBad headline1("Celery Stalks at Midnight");
StringBad headline2("Lettuce Prey");
StringBad sports("Spinach Leaves Bowl for Dollars");
cout << "headline1: " << headline1 << endl;
cout << "headline2: " << headline2 << endl;
cout << "sports: " << sports << endl;
callme1(headline1);
cout << "headline1: " << headline1 << endl;
callme2(headline2);
cout << "headline2: " << headline2 << endl;
cout << "Initialize one onject to another:\n";
StringBad sailor = sports;
cout << "sailor: " << sailor << endl;
cout << "Assign one object to another:\n";
StringBad knot;
knot = headline1;
cout << "knot: " << knot << endl;
cout << "Exiting the block.\n";
}
cout << "End of main()\n";
std::cin.get();
return 0;
}
void callme1(StringBad & rsb)
{
cout << "String passed by reference:\n";
cout << " \"" << rsb << "\"\n";
}
void callme2(StringBad sb)
{
cout << "String passed by value:\n";
cout << " \"" << sb << "\"\n";
}

程序开始时还是正常的,但逐渐变得异常,最终导致了灾难性结果。

首先来看正常的部分。 构造函数指出自己创建了3个StringBad对象,并为这些对象进行了编号,然后程序使用重载运算符<<列出这些对象:

1: "Celery Stalks at Midnight"object created

2: "Lettuce Prey" object created

3: "Spinach Leaves Bowl forDollars" object created

headline1: Celery Stalks at Midnight

headline2: Lettuce Prey

sports: Spinach Leaves Bowl for Dollars

然后,程序将headline1传递给callme1()函数,并在调用后重新显示headline1。 代码如下:

     callme1(headline1);

     cout<< "headline1: " << headline1 << endl;

下面是运行结果:

String passed by reference:

 "Celery Stalks at Midnight"

headline1: Celery Stalks at Midnight

这部分代码看起来也正常。

但随后程序执行了如下代码:

     callme2(headline2);

     cout<< "headline2: " << headline2 << endl;

这里,callme2()按值(而不是按引用)传递headline2,结果表明这是一个严重的问题!

String passed by value:

 "Lettuce Prey"

"Lettuce Prey" object deleted, 2left

headline2: 葺葺葺葺葺葺葺葺軮

将headline2作为函数参数来传递从而导致析构函数被调用。

另外,下面的代码:

StringBad sailor= sports;

这使用的是哪个构造函数呢?不是默认构造函数,也不是参数为const char*的构造函数。记住,这种形式的初始化等效于下面的语句:

StringBad sailor = StringBad(sports); //使用sports的构造函数

因为sports的类型为StringBad,因此相应的构造函数原型应该如下:

StringBad(const StringBad &);

当您使用一个对象来初始化另一个对象时,编译器将自动生成上述构造函数(称为复制构造函数,因为它创建对象的一个副本)。 自动生成的构造函数不知道需要更新静态变量num_string,因此会将计数方案搞乱。 实际上,这个例子说明的所有问题都是由编译器自动生成的成员函数引起的。

1.1. 特殊成员函数

StringBad类的问题是由特殊成员函数引起的。 这些成员函数是自动定义的,就StringBad而言,这些函数的行为与类设计不符。 具体地说,C++自动提供了这些成员函数:

默认构造函数,如果没有定义构造函数;

默认析构函数,如果没有定义;

复制构造函数,如果没有定义;

赋值运算符,如果没有定义;

地址运算符,如果没有定义。

(1) 默认构造函数:

如果没有提供任何构造函数,C++将创建默认构造函数。 例如,加入定义了一个Klunk类,但没有提供任何构造函数,则编译器将提供下述默认构造函数:

Klunk::Klunk() { } //隐式默认构造函数

Klunk lunk; //调用默认构造函数

默认构造函数使Lunk类似于一个常规的自动变量,也就是说,它的值在初始化时是未知的。

如果定义了构造函数,C++将不会定义默认构造函数。 如果希望在创建对象时不显式地对它进行初始化,则必须显式地定义默认构造函数。这种构造函数没有参数,但可以使用它来设置特定地值:

Klunk::Klunk() //显式默认构造函数

{

klunk_ct=0;

}

带参数的构造函数也可以是默认构造函数,只要所有参数都有默认值。 例如,Klunk类可以包含下述内联构造函数:

Klunk(int n = 0) {klunk_ct = n;}

但只能有一个默认构造函数。 也就是说,不能这样做:

Klunk() {klunk_ct = 0;} //构造函数#1

Klunk(int n =0) {klunk_ct=n;} //具有二义性的构造函数#2

例如:

Klunk kar(10); //明确地与#1匹配

Klunk bus; //与两个构造函数均可匹配

第二个声明既与构造函数#1(没有参数)匹配,也与构造函数#2(使用默认参数0)匹配。

(2) 复制构造函数

复制构造函数用于将一个对象复制到新创建的对象中。 类的复制构造函数的原型如下:

Class_name(const Class_name &);

它接受一个指向类对象的常量引用作为参数。 例如,StringBad类的复制构造函数原型如下:

StringBad(const StringBad &);

对于复制构造函数,需要知道两点:何时调用和有何功能。

(3) 何时调用复制构造函数

新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。 这在很多情况下都可能发生,最常见的情况是将新对象显式地初始化为现有地对象。例如,假设motto是一个StringBad对象,则下面4种声明都将调用复制构造函数:

StringBadditto(motto); //调用StringBad(const StringBad &)

StringBad metoo =motto; //调用StringBad(const StringBad &)

StringBad also = StringBad(motto); //调用StringBad(const StringBad &)

StringBad*pStringBad = new StringBad(motto);

                      ////调用StringBad(constStringBad &)

其中中间的两种声明可能会使用复制构造函数直接创建metoo和also,也可能使用复制构造函数生成一个临时对象,然后将临时对象的内容赋给metoo和also,这取决于具体的实现。 最后一种声明使用motto初始化一个匿名对象,并将新对象的地址赋给pstring指针。

每当程序生成了对象副本时,编译器都将使用复制构造函数。 由于按值传递对象将调用复制构造函数,因此应该按引用传递对象。 这样可以节省调用构造函数的时间以及存储新对象的空间。

(4) 默认的复制构造函数的功能

默认的复制构造函数逐个复制非静态成员(成员复制也称为浅复制),复制的是成员的值。

StringBad sailor = sports;

与下面代码等价(由于私有成员是无法访问的,因此这些代码不能通过便于):

StringBad sailor;

sailor.str=sports.str;

sailor.len=sports.len;

如果成员本身就是类对象,则将使用这个类的复制构造函数来复制成员对象。 静态函数(如num_strings)不受影响,因为它们属于整个类,而不是各个对象。

1.2. 回到StringBad:复制构造函数的哪里出了问题

当callme2()被调用时,复制构造函数被用来初始化callme2()的形参,还被用来将对象sailor初始化为对象sports。 默认的复制构造函数不说明其行为,因此它不指出创建过程,也不增加计数器num_strings的值。但析构函数更新了计数,并且在任何对象过期时都将被调用,而不管对象是如何被创建的。 这是一个问题,因为这意味着程序无法准确地记录对象计数。 解决方法是提供一个对计数进行更新地显式复制构造函数:

StringBad::StringBad(const StringBad & s)

{

num_strings++;

...

}

第二个异常之处更微妙,也更危险:

headline2: 葺葺葺葺葺葺葺葺軮

原因在于隐式复制构造函数是按值进行复制的。 隐式复制构造函数的功能相当于:

sailor.str = sports.str;

这里复制的并不是字符串,而是一个指向字符串的指针。 也就是说,将sailor初始化为sports后,得到的是两个指向同一个字符串的指针。 当operator<<()函数使用指针来显示字符串时,这并不会出现问题。但当析构函数被调用时,这将引发问题。

(1) 定义一个显式复制构造函数以解决问题

解决类设计种这种问题的方法时进行深度复制(deep copy)。 也就是说,复制构造函数应当复制字符串并将副本的地址赋给str成员,而不仅仅是复制字符串地址。 这样每个对象都有自己的字符串,而不是引用另一个对象的字符串。 调用析构函数时都将释放不同的字符串,而不会试图去释放已经被释放的字符串。可以这样编写String的复制构造函数:

StringBad::StringBad(const StringBad & st)

{

num_strings++;         //处理静态成员更新

len= st.len;          //相同长度

str= new char[len + 1];    //分配空间

std::strcpy(str,st.str);   //将字符串复制到新位置

cout<< num_strings << ":\"" << str

     << "\" object created\n";

}

必须定义复制构造函数的原因在于,一些类成员是使用new初始化的、指向数据的指针,而不是数据本身。

1.3. StringBad的其他问题:赋值运算符

C++允许类对象赋值,这是通过自动为类重载赋值运算符实现的。 这种运算符的原型如下:

Class_name & Class_name::operator=(constClass_name &);

它接受并返回一个指向类对象的引用。 例如,StringBad类的赋值运算符的原型如下:

StringBad & StringBad::operator=(constStringBad &);

(1) 赋值运算符的功能以及何时使用它

StringBad headline1(“Celery Stalks atMidnight”);

StringBad knot;

Knot = headline1; //赋值运算符被调用

初始化对象时,并不一定会使用赋值运算符:

StringBad metoo=knot; //可能使用复制构造函数,也可能是赋值运算符

这里,metoo是一个新创建的对象,被初始化为knot的值,因此使用复制构造函数。 然而,正如前面指出的,实现时也可能分两步来处理这条语句:使用复制构造函数创建一个临时对象,然后通过赋值将临时对象的值复制到新对象中。这就是说,初始化总是会调用复制构造函数,而使用=运算符时也允许调用赋值运算符。

(2) 解决赋值的问题

对于由于默认赋值运算符不合适导致的问题,解决办法时提供赋值运算符(进行深度复制)定义。 其实现与复制构造函数相似,但也有一些差别。

由于目标对象可能引用了以前分配的数据,所以函数应使用delete[]来释放这些数据。

函数应当避免将对象赋给自身;否则,对对象重新赋值前,释放内存操作可能删除对象的内容。

函数返回一个指向调用对象的引用。

StringBad & StringBad::operator=(const StringBad & st)

{

if (this == &st)               //对象赋值给自身

     return *this;               //结束

delete[] str;                  //释放老字符串

len= st.len;

str= new char[len + 1];        //为新字符串开辟空间

std::strcpy(str,st.str);       //复制字符串

return *this;                  

}

如果地址相同,程序将返回*this,然后结束。 如果地址不同,函数将释放str指向的内存,这是因为稍后把一个新字符串的地址赋给str。 如果不首先使用delete运算符,则上述字符串将保留在内存中。赋值操作并不创建新的对象,因此不需要调整静态数据成员num_strings的值。

原文地址:http://blog.51cto.com/13869630/2142548

时间: 2024-08-26 23:04:52

C++-类和动态内存分配 大发彩_票平台开发的相关文章

超详细java中 大发彩_票平台搭建 的ClassLoader详解

ClassLoader翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说却非常常见.理解ClassLoader的加载机制,也有利于我们编写出更高效的代码.ClassLoader的具体作用就是将class文件加载到jvm虚拟机中去,程序就可以正确运行了.但是,jvm启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载.想想也是的,一次性加载那么多jar包那么多class,那内存不崩溃.本文的目的也是学习ClassLoader这种加载机制. 大发彩

分享一篇耗子源码论坛大发彩_票平台搭建教程

大发彩_票平台搭建 Q1446595067在之前的工作,一直是以Android为主,前端为辅.经过了几个项目,也基本了解了前后端数据交互到底是怎么回事儿.但是在做项目的过程中经常有这个问题,项目初期需求定稿开始开发工作期间,服务端的同事需要搭建数据库,搭框架之类的工作,而同样的移动端或前端也开始撘架构或写页面,在写页面的时候不免要用到数据.此时,服务端的同事很可能因为工作进度还没有完成接口的开发.这个时候,如果不会服务端开发的话,我们一般只能在本地模拟一些假数据来使用,或者等着后台提供接口-.但

下载大发彩_票平台

下载大发彩_票平台地址一:[hubawl.com]狐霸源码论坛地址二:[bbscherry.com] 项目开发完毕后,需要将代码放到服务器上,这样用户才能访问.接下来我们一步一步来进行一波部署操作 打包上传代码 项目开发完毕,在部署之前需要再配置文件中将 ALLOWED_HOSTS配置设置为:当前服务器IP或*,如: ALLOWED_HOSTS = ["",]这个配置就是允许别人通过哪个ip访问你,表示所有ip 上线时还要将配置中的DEBUG改为False 不同系统如何传代码 wind

Effective Java-创建 大发彩_票平台出租 和销毁对象

一.考虑用静态方法代替构造器 ? 类可以通过静态工厂方法来提供它的客户端,而不是通过构造器. 优势: ? 1.它们有名称,可以根据名称将创建对象的行为表达的更清楚. ? 2.可以不必在每次调用它们的时候都返回一个新对象.这使得不可变类可以使用预先构建好的实例,或者将构建好的实例缓存起来,进行重复利用. ? 3.它们可以返回原返回类型的任何子类型的对象.API可以返回对象,同时又不会使对象的类变成公有的.以这种方式隐藏实现细节. ? 4.在创建参数化实例的时候,它们使代码变得简洁. 缺点: ? 1

Python的time库和文本进度条 大发彩_票平台搭建

大发彩_票平台搭建 地址一:[hubawl.com]狐霸源码论坛地址二:[bbscherry.com] 是Python中处理时间的标准库1.time库包括三类函数 时间获取:time() ctime() gmtime()时间格式化:strftime() strptime()程序计时:sleep(), perf_counter()2.时间获取 3.时间格式化 4.程序计时 5.进度条实例 #textProBarV1.pyimport timescale = 50print("执行开始".

C++ primer plus读书笔记——第12章 类和动态内存分配

第12章 类和动态内存分配 1. 静态数据成员在类声明中声明,在包含类方法的文件中初始化.初始化时使用作用域运算符来指出静态成员所属的类.但如果静态成员是整形或枚举型const,则可以在类声明中初始化. P426-P427类静态成员的声明和初始化 //strnbad.h class StringBad { private: static int num_strings; … }; //strnbad.cpp int StringBad::num_strings = 0; 不能在类声明中初始化静态

《C++ Primer Plus》读书笔记之十—类和动态内存分配

第12章 类和动态内存分配 1.不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存.可以在类声明之外使用单独的语句进行初始化,这是因为静态类成员是单独存储的,而不是对象的组成部分.注意:静态成员在类声明中声明,在包含类方法的文件中初始化.初始化时使用作用域操作符来指出静态成员所属的类.但如果静态成员是整型或枚举型const,则可以在类声明中初始化. 2.当使用一个对象来初始化另一个新建对象时,编译器将自动生成一个复制构造函数,因为它创建对象的一个副本.复制构造函数的

类和动态内存分配

类和动态内存分配 整理自<C++ Primer Plus> 1. 动态内存和类 静态类成员有一个特点:无论创建了多少对象,程序都只创建一个静态类变量副本.也就是说,类的所有对象共享同一个静态成员.静态数据成员在类声明中声明,在包含类方法的文件中初始化. 复制构造函数用于将一个对象复制到新创建的对象中.也就是说,也就是说,它用于初始化过程中(包括按值传递参数),而不是常规的赋值过程中.按值传递意味着创建原始变量的一个副本.由于按值传递对象将调用复制构造函数,因此应该按引用传递对象.这样可以节省调

python中的全大发彩局票网站开发变量与局部变量的区别

全局变量与大发彩局票网站开发haozbbs.comQ1446595067局部变量的本质区别在于作用域: 全局变量是在整个py文件中声明的,全局范围内都能访问: 局部变量是在某个函数中声明的,只能在该函数中调用它,如果试图在超出范围的地方调用,程序就会崩掉. 如果在函数内部定义某个与全局变量一样名称的局部变量,就可能导致意外的效果,不建议这样使用,这样会使程序不健全.例1: def fun(x):y=2print("乘法的运行结果:",x*y) num1=1print("初始n