C++ primer plus 类和动态内存分配

发布于:2025-03-24 ⋅ 阅读:(23) ⋅ 点赞:(0)

目录

文章目录

前言

一  动态内存分配和类

二  特殊的成员函数 

三  利用String类进行复习

四  在构造函数里面使用new的注意事项

五  有关返回对象的说明

六  使用指针指向对象 

总结


前言

这里将要讲述类和动态内存分配


一  动态内存分配和类

我们都知道我们在创建一个数组的时候,长度是固定的,这个时候难免会有内存的浪费和内存的不够来使我们头疼,这个时候我们就要用到动态内存分配才自动分配内存,使我们的内存的大小刚刚好,由于很久都没有使用new和delete了,所以我们就用一个程序来复习一下new,delete和静态存储
头文件

#ifndef __TOOL_H__
#define __TOOL_H__

using namespace std;
#include<ostream>
class StringBad {
    private:
      char *str;
      int len;
      static int num_strings;
    public:
      StringBad();
      StringBad(const char* S);
      ~StringBad();
      friend ostream& operator<<(ostream& os, const StringBad& st);
};

#endif

工具函数

#include"tool.h"
#include<cstring>
#include<iostream>
using namespace std;

int StringBad::num_strings = 10;

StringBad::StringBad(const char* s){
    len = strlen(s);
    str = new char[len + 1]; // 加1放\0
    strcpy(str, s);
    num_strings++;
    cout << num_strings << ": \"" << str << ": \"" <<endl;

}

StringBad::StringBad(){
    len = 4;
    str = new char[4];
    strcpy(str,"C++");
    num_strings++;
    cout << num_strings << " : \" " << str << " : \" " <<endl;
}

StringBad::~StringBad(){
    cout << "\"" << str << "\" object delete " << endl;
    delete []str;
}

ostream& operator<<(ostream& os, const StringBad& st){
    os << st.str;
    return os;
}

main函数

#include<iostream>
#include"tool.h"
using namespace std;

void test1(StringBad& a);
void test2(StringBad b);

int main(){
    StringBad head("hello world");
    StringBad a("Good moring");
    StringBad b;

    cout << "1 : " << head;
    cout << "2 : " << a;
    cout << "3 : " << b;

    test1(head);
    test2(head);
    return 0;
}

void test1(StringBad& a){
    cout << endl;
    cout << " is yinyong chaundi " << a <<endl; 
}

void test2(StringBad b){
    cout << endl;
    cout << "is value chaundi" << b << endl;
}

这里要体现出两个知识点
1 new和delete使用
这里new后面的数组进行加1是因为我们还要留一个空间保存\0
delete要删除数组的话要记得加一个[ ]

2 静态存储
首先我们要知道我们类在头文件里面只是声明,并没有开始开辟内存进行存储,所以我们不可以直接在类里面进行赋值操作
但是我们有两个方法来书写常量
1  利用static开辟到只读数据段  2  利用enum来进行定义常量

这里不可以在类里面进行赋值,不能在类声明中初始化静态成员变量,这 是因为声明描述了如何分配内存,但并不分配内存,这个只是给了一个格式
对于静态类成员,可以在类声明之外使用单独的语句来进行初始化, 这是因为静态类成员是单独存储的,而不是对象的组成部分
在代码体现在这里

int StringBad::num_strings = 10;

初始化是在方法文件中,而不是在类声明文件中进行的这是因为类声明位于头文件中,程序可能 将头文件包括在其他几个文件中。如果在头文件中进行初始化,将出现多个初始化语句副本,从而引发错误
所以这里就需要注意,这个静态成员要放到方法文件里面进行初始化

对于非成员函数的按值传递和按引用传递危机

#include<iostream>
#include"tool.h"
using namespace std;

void test1(StringBad& a);
void test2(StringBad b);

int main(){
    StringBad head("hello world");
    StringBad a("Good moring");
    StringBad b;

    cout << "1 : " << head;
    cout << "2 : " << a;
    cout << "3 : " << b;

    test1(head);
    test2(head);
    return 0;
}

void test1(StringBad& a){
    cout << endl;
    cout << " is yinyong chaundi " << a <<endl; 
}

void test2(StringBad b){
    cout << endl;
    cout << "is value chaundi" << b << endl;
}

当我们只调用test1这个函数的话,我们程序是正常运行的

这个是没有问题的

这个后面有了问号,而且这个顺序也乱了,这个是为什么呢?

这个就类似是把一个成员进行赋值,然后这个就会去找这个这个构造函数,但是没有找到,然后编译器就会自动生成上述的构造函数,但是这个自动生成的构造函数没有用到new,但是当我们把这个对象结束的时候,会去找析构函数,我们这里提供了析构函数,那么这里就会去用这个析构函数,那么这个复制构造函数都没有用到new来弄,我们就直接用了delete,就会出现错误

所以这里我们要知道我们在传参的时候,尽量使用引用,要不然会出现意想不到的错误,而且还要知道编译器的行动,它会生成一个复制构造函数来进行操作

二  特殊的成员函数 

在类中我么在如果没有自定义一些函数的话,编译器会给我们默认的函数,这些函数有哪一些呢?

首先默认析构函数和默认构造函数我们都知道了,然后我么要知道我们不知道的函数

1  复制构造函数
我们可以自定义复制构造函数来解决刚刚我们所遇到的问题
复制构造函数的定义格式
classname(const classname & 名字)
复制构造函数一般都是我们在进行初始化的时候,也就是按值传递的时候,又或者是返回一个类的对象的时候,函数的调用一般都会有,但是如果我们用编译器给我们提供默认复制构造函数,但是我们的析构函数与默认复制构造函数不匹配的话,那么就会报错,注意,初始化和赋值是不一样的
,按值传递的时候,是需要调用复制构造函数的,也就是说为了避免内存的开销和时间的开销,我么尽可能的使用引用传递和返回

头文件

#ifndef __TOOL_H__
#define __TOOL_H__

using namespace std;
#include<ostream>
class StringBad {
    private:
      char *str;
      int len;
      static int num_strings;
    public:
      StringBad();
      StringBad(const char* S);
      StringBad(const StringBad& a);
      ~StringBad();
      friend ostream& operator<<(ostream& os, const StringBad& st);
};

#endif

工具函数

#include"tool.h"
#include<cstring>
#include<iostream>
using namespace std;

int StringBad::num_strings = 10;

StringBad::StringBad(const char* s){
    len = strlen(s);
    str = new char[len + 1]; // 加1放\0
    strcpy(str, s);
    num_strings++;
    cout << num_strings << ": \"" << str << ": \"" << endl;

}

StringBad::StringBad(const StringBad& a){
    len = a.len;
    str = new char[len + 1];
    strcpy(str, a.str);
    num_strings++;
    cout << num_strings << ": \"" << str << ": \"" << endl;
}

StringBad::StringBad(){
    len = 4;
    str = new char[4];
    strcpy(str,"C++");
    num_strings++;
    cout << num_strings << " : \" " << str << " : \" " <<endl;
}

StringBad::~StringBad(){
    cout << "\"" << str << "\" object delete " << endl;
    delete []str;
}

ostream& operator<<(ostream& os, const StringBad& st){
    os << st.str;
    return os;
}

 main函数

#include<iostream>
#include"tool.h"
using namespace std;

void test1(StringBad& a);
void test2(StringBad b);

int main(){
    StringBad head("hello world");
    StringBad a("Good moring");
    StringBad b;

    cout << "1 : " << head;
    cout << "2 : " << a;
    cout << "3 : " << b;

    test1(head);
    test2(head);
    return 0;
}

void test1(StringBad& a){
    cout << endl;
    cout << " is yinyong chaundi " << a <<endl; 
}

void test2(StringBad b){
    cout << endl;
    cout << "is value chaundi" << b << endl;
}

我来看一下运行结果
 我们不难看到这个按值传递的是时候,这个就会到复制构造函数里面进行操作

深度思考
上面问题有没有可能是复制构造函数和=运算符函数同时引起的?

也就是说当我们进行初始化的时候,会调用复制构造函数,然后进行=的处理的时候,就会调用=重载运算符函数来进行操作,那么为了我们的平台在每一个都可以得到相同的结果,所以我么的复制构造函数和=重载运算符都要进行编写

那么我们该如何进行编写呢?
首先我们要利用我们之前所学的运算符重载函数的写法

StringBad& StringBad::operator=(const StringBad& st){
    if(this == &st)
      return *this;

     delete []str;
     len = st.len;
     str = new char[len + 1];
     strcpy(str, st.str);
     return *this;
}

首先为什么前面会有一个delete呢?
 我们进行这个操作的时候,我么只要创建了类的对象是不是要去找构造函数,然后用new开辟一个新的空间,如果我么metoo开辟一个,knot也开辟一个,那么我们在进行下面的操作的时候,是不是会浪费内存,又有人问了,为什么不直接把这个值赋值到原有的空间,首先我们开辟的空间大小不知道是多少,我们要根据传过来的来开辟,进行空间的完全利用

然后为什么上面还有一个if语句呢?
为了防止有自己赋值给自己的这种sb操作,如果不写这个的话,那么就会导致我们把先前的那个删掉了,我们在进行复制的时候,就没有东西复制了,因为你都清理,所以自己给自己赋值的时候,需要额外加一个情况

三  利用String类进行复习

#ifndef __TOOL_H__
#define __TOOL_H__

using namespace std;
#include<ostream>
class String {
    private:
      char *str;
      int len;
      static int num_strings;
      static const int MAXSIZE = 100;
    public:
      String();
      String(const char* S);
      String(const String& a);
      ~String();
      int length() const{return len;}

      String &operator=(const String& st);
      String &operator=(const char* st);
      char operator[](int i);
      const char &operator[](int i) const;

      friend bool operator<(const String &str1, const String &str2);
      friend bool operator>(const String &str1, const String &str2);
      friend bool operator==(const String &str1, const String &str2);
      friend ostream& operator<<(ostream& os, const String& st);
      friend istream& operator>>(istream& is,String& st);
      static int howmany();//不可以通过类的对象直接访问
};

#endif
#include"String.h"
#include<cstring>
#include<iostream>
using namespace std;

int String::num_strings = 10;

int String::howmany(){
    return num_strings;
}

String::String(const char* s){
    len = strlen(s);
    str = new char[len + 1]; // 加1放\0
    strcpy(str, s);
    num_strings++;
}

String::String(const String& a){
    len = a.len;
    str = new char[len + 1];
    strcpy(str, a.str);
    num_strings++;
}

String::String(){
    len = 0;
    str = new char[1];
    strcpy(str,"\0");
    num_strings++;
}

String::~String(){
    delete []str;
}

ostream& operator<<(ostream& os, const String& st){
    os << st.str;
    return os;
}

String& String::operator=(const String& st){
    if(this == &st)
      return *this;

     delete []str;
     len = st.len;
     str = new char[len + 1];
     strcpy(str, st.str);
     return *this;
}

String& String::operator=(const char* st){
    delete []str;
    len = strlen(st);
    str = new char[len + 1];
    strcpy(str, st);
    return *this;
}

char String::operator[](int i){
    return str[i];
}

const char& String::operator[](int i) const{
    return str[i];
}

bool operator<(const String &str1, const String &str2){
    return (strcmp(str1.str, str2.str) > 0);
}

bool operator>(const String &str1, const String &str2){
    return (strcmp(str1.str, str2.str) < 0);
}

bool operator==(const String &str1, const String &str2){
    return (strcmp(str1.str, str2.str) == 0);
}

istream& operator>>(istream& is, String& st){
    char temp[String::MAXSIZE];
    is.get(temp, String::MAXSIZE);
    if(is) st = temp;
    while(is && is.get() != '\n')
    continue;
    
    return is;
}

1  输入和输出的书写
输入和输出是要利用到ostream和istream这两个类的,然后我们使用引用就可以直接使用其中的对象,然后我们就可以使用里面的方法了,很简单
记忆方法
我们可以书写一个cin.get这种获取行,然后我们上面定义了一个这个对象,我们就可以直接把这个cin换成is,输出也是这样

2  =赋值运算符号
这里是考虑到一个是string和一个string对象进行相互直接的赋值,还有一个是利用char数组进行赋值的情况

1  对象和对象赋值的情况
我们要考虑到,我们之前是用了new进行开辟了一个空间的,因为我们没创建一个对象就要调用一个构造函数,这个构造函数是有new进行开辟空间的,然后我们要把直接的那可以空间进行删除,然后把那个赋值的对象的字符串进行拷贝,我们删除原来那一个之后要重新开辟一个新的空间给这个对象,如果是自己赋值自己就直接返回this指针就好了

2  对象和char数组进行赋值
这个时候我们不需要进行自己赋值给自己的操作了,我们直接进行删除之前的,然后把字符串的产长度进行判断出来利用strlen,然后进行赋值就好了

3  比较运算符的重载
这个就是利用cstring里面的比较函数来进行判断哪一个更大

4  [ ] 运算符的重载
这个就是利用传入一个i然后返回对应的str[ i ]的元素,就是获取对应的元素

5  构造函数与析构函数就是根据你程序的需要来自己定义了

四  在构造函数里面使用new的注意事项

1  当我们在构造函数里面使用了new在析构函数也要使用delete,进行一一对应的关系
new---delete     new[ ]---delete[ ]

2  当我们创建了多个构造函数的时候,必须都用new或者不用new,因为析构函数就只有一个,所以只有一个delete来进行对应
在进行初始化的时候,应该定义一个复制构造函数,来进行深度复制一个对象给另外一个对象,因为我们是用new进行创建的,然后再用析构函数进行释放,如果我们用默认复制构造函数的话,那么就会报错,因为默认复制构造函数里面没有new,但是析构函数里面由delete

3  在一个类里面会出现类里面的类
    如下

class Data{
   String a;
   string b; 
  };

这里我们的String和string是已经定义过的类,那么就是由他自己本身的类的构造函数进行初始化了,所以这里就不需要进行编写构造函数给他们进行赋值了

4  在C++里面尽可能的使用nullptr来表示空指针,delete可以用于空指针的的清除操作,虽然没有用new进行创建空间,但是还是可以进行清除操作

五  有关返回对象的说明

1  利用返回const的引用来提高效率

我们可以以这两个程序为例子来进行梳理自己的思路
这里有三点需要说明。首先,返回对象将调用复制构造函数,而返回引用不会。因此,第二个版本所 做的工作更少,效率更高。其次,引用指向的对象应该在调用函数执行时存在 ,这个最后一个是什么意思呢?就是我们在使用引用的时候,返回的时候应该是存在的,而不是不存在的

2  返回的时候,什么是加const,什么时候不加const

这里我们就不可以返回const的引用,因为我们返回const的引用的话,那就说明这三个值都是const类型的,就会无法修改值,为了修改,我们就不加上const

3  为什么不在返回值利用ostream,而是引用

 因为我们如果返回值得话,不是引用,这个时候是要调用ostream的复制构造函数的,但是ostream的复制构造函数是私有的不是共有的,所以返回这个会出现错误

4  返回对象而不是引用
这个一般是在算术表达式里面体现

首先我们要使这个force1和2都保持不变就要用const,但是不可修改,所以用值传递
其次就是在进行操作的时候我们返回的变量需要用到一个临时变量进行存储才可以返回到本身那个变量上,这个情况难免回调用复制构造函数进行开销

5  什么时候返回值要加const
就上面而言,这个三个都是允许的,为什么?
第二个force1和force2相加可以产生一个临时变量存储,然后这个传给net,然后这个值覆盖掉前面两个数相加
这个显然是不合理的,所以我们就直接加一个const,表示这个是不可以修改的,如果使用第二个就会报错,为什么?因为左值不可以为常量


六  使用指针指向对象 

这个里面很多都是跟之前指针语法差不多,这里直接讲解一些不知道的
首先就是我们再对类使用new的时候,就是开辟一个空间存储一个对象
这个时候,如果对象的构造函数里面析构函数里面由new来开辟一个空间放东西的话就是这样的

 

这个new后面表示要开什么类型的类,然后括号里面表示要调用哪一个构造函数,根据里面的类型进行配对,如果没有就是默认构造函数当我们使用delete删除的时候,对象由于释放调用了析构,然后指针指向的是通过delete释放
后面的new定位运算符好像不怎么常用,这里再后续用到再继续学习 


总结

1  动态内存分配和类
首先我们复习了new和delete的用法

其次就是静态存储的一些操作,如enum和static这两个使用的方法

最后就是这个常量的赋值一般都是在方法文件里面,因为如果我们定义在class里面,那么每一次引用就要进行一次初始化,这样会出现多个初始化副本,这样就会出现重名错误,我们要牢记,这个常量不是对于每个成员的,而是公用的

2  特殊成员函数
默认构造和析构函数,复制构造函数,运算符重载函数
首先复制构造函数一般都是在初始化的时候进行调用的,比如值传递
在进行值传递得时候,我们如果新参是一个类的类型的时候,是要去调用构造函数的,如果我们用了new个这个对象开辟了空间,在进行初始化的时候要先删除再进行复制赋值


3  string复习
首先我们知道每一个符号重载函数是如何进行书写了,还有对析构和构造进行加深印象

4  学习了new,delete,返回值和指针的注意事项


网站公告

今日签到

点亮在社区的每一天
去签到