c++ 类和对象

基本概念

存在自身的属性、行为,封装在class

类是抽象的,对象是具象的

对象是由类实例化而来的

类对于某个事物的描述是抽象的,类是对一个事物的一个描述而已
操作的某类事物中一个或者多个具体的食物,就是称为对象

类的定义

属性:定义

行为:函数/方法

class name{
   
访问控制符:
	成员变量
	成员函数
}
访问控制符有 public private protected

```cpp
class Animal{
public:
    int age;
    char name[32];

    void jiao(const char *voice){
        std::cout<<name<<":"<<voice<<std::endl;
    }
};
  • 类的首字母一般大写 大驼峰写法
  • 访问控制符后续细说

类的基本使用

对象的实例化

实例化普通对象

Animal cat; //一个实例化的对象

使用数组实例化多个普通对象

Animal cats[10];

定义一个指针变量

Animal *cat;
//指针变量不是一个类的实例化对象,本质是一个指针。定义一个类的指针变量根本就没有实例化一个对象

类的实例化方法还有,后续继续

例子:

class Animal{
   
public:
    int age;
    char name[32];

    void jiao(const char *voice){
   
        std::cout<<name<<":"<<voice<<std::endl;
    }
};

int main() {
   
    Animal dog;
    dog.age=18;
    // 清零
    memset(dog.name,0,sizeof(dog.name));
    // 赋值
    strcpy(dog.name,"xiaohu");
    dog.jiao("wang wang");
    return 0;
}

访问控制符

给成员变量和方法定义访问级别

  • public 公开的,类的内部和外部都可以访问到变量和方法,还有继承。。。
  • private 私有的,只有类的内部可以访问到变量和方法,继承。。。
  • protected 保护的,只有类的内部可以访问到变量和方法,继承。。。
class Teacher{
   
public:
    char name[32];

    void set_age(int age){
   
        if(age>200 || age<0) {
    // 阈值判断   函数形参合法性检查
            std::cout<<"age error"<<std::endl;
            return;
        }
        _age=age; // 在类的内部访问private成员变量
    }

    int get_age(){
   
        if(_age){
   
            return _age;
        }
        return 0; // 0代表age不存在
    }
private:
    int _age;
};


    Teacher t;
    memset(t.name,0,sizeof(t));
    strcpy(t.name,"三哥");
    
    //t._age=18;   错误的
    
    t.set_age(54);
    std::cout<<t.get_age()<<std::endl;

面向对象程序设计方法

面向过程程序设计:数据结构+算法

  • 用户需求简单而固定

特点

  • 分析解决问题所需的步骤
  • 利用函数实现各个步骤
  • 依次调用函数解决问题

问题

  • 软件可重用性差
  • 软件可维护性差

面向对象程序设计

  • 属性:静态特征,可以用某种数据来描述
  • 方法:动态特征,对象所表现的行为或具有的功能

特点

  • 直接分析用户需求中涉及的各个实体
  • 在代码中描述现实世界的实体
  • 在代码中关联各个实体协同工作解决问题

优势

  • 构建的软件能适应用户需求的不断变化

三大特征

  • 封装

    • 把变量(属性)和函数(方法)合成一个整体,封装在一个类中
    • 尽可能隐蔽对象内部的细节。对外形成一个边界,只保留有限的对外接口与外部发生联系
    • 对变量和函数进行访问控制,保证数据的安全性
  • 继承

  • 多态

实例

// Box.h
#ifndef C___BOX_H
#define C___BOX_H
class Box {
   
private:
    int _l,_w,_h,_s,_v;
public:
    void set_l(int l);
    void set_w(int w);
    void set_h(int h);
    int get_l();
    int get_w();
    int get_h();
    int get_s();
    int get_v();
};
#endif //C___BOX_H



// Box.cpp
#include "Box.h"
#include "iostream"
void Box::set_l(int l) {
   
    if(l<0){
   
        std::cout<<"l error"<<std::endl;
        return;
    }
    _l=l;
}
void Box::set_w(int w) {
   
    if(w<0){
   
        std::cout<<"w error"<<std::endl;
        return;
    }
    _w=w;
}
void Box::set_h(int h) {
   
    if(h<0){
   
        std::cout<<"h error"<<std::endl;
        return;
    }
    _h=h;
}

int Box::get_l() {
   
    return _l;
}
int Box::get_w() {
   
    return _w;
}
int Box::get_h() {
   
    return _h;
}

// 先判断长宽高是否存在 然后在计算 这里没有实现
int Box::get_s() {
   
    return 2*(_l*_h+_l*_w+_h*_w);
}
int Box::get_v() {
   
    return _l*_w*_h;
}


// main.cpp
 Box b;
    b.set_l(15);
    b.set_h(5);
    b.set_w(5);
    std::cout<<"s:"<<b.get_s()<<std::endl;
    std::cout<<"v:"<<b.get_v()<<std::endl;

构造函数和析构函数

构造函数

类的属性在定义的时候不可以初始化

class Dog{
   
public:
	int age=18; // 这是不允许的
};

构造函数(constructor)处理对象的初始化,特殊的成员函数,实例化对象的时候自动调用构造函数,作用就是在类实例化的时候初始化成员变量

定义

与类名相同的特殊成员函数 即为构造函数
在定义的时候可以有参数,也可以没有参数
没有任何返回类型的声明
构造函数要在外部可以访问到 public

class Dog{
   
public:
/*
默认构造函数:如果构造函数没有被定义,编译器会自动生成一个如下的构造函数(我们没有写构造函数的时候才会自动生成)
*/
	Dog(){
   }
};



// 实例
class Dog{
   
public:
//无参数构造函数
    Dog(){
   
        std::cout<<"Dog"<<std::endl;
    }
    Dog(int age,int id){
   
        std::cout<<age<<"-"<<id<<std::endl;
    }
private:
    int _age;
    char name[50];
};

    Dog d1;  //一次
    Dog d2[4]; //四次
    //不是实例化一个对象,而是声明一个函数 返回值是Dog 名字是d3  形参列表为空
    Dog d3();

	Dog d5(1,2); //实例化对象 只调用相应的有两个int形参的调用函数

初始化成员列表

  • 由逗号分割的初始化列表组成
  • 数据成员的名称为mdata,并需要初始化为val 写法 mdata(val)
    Dog(int id,int num,int age):_id(id),_num(num),_age(age){
   
        std::cout<<"Dog:"<<std::endl;
    }

作用

  • 对类属性为引用时进行初始化 一个构造函数为引用初始化后,后续的构造函数都要进行
  • const修饰的成员变量进行初始化
  • 一个类中实例化另外一个类

调用 Dog d 的时候内存已经分配空间了,此时在构造函数体内部进行的是赋值而不是初始化,利用冒号即是初始化

class C{
   
public:
    int x;
    C(int s){
   
        x=s;
    }
};
// 类中实例化一个类
class B{
   
public:
    int a;
    C n;
    B():a(1),n(50){
   }
};

//如果下面则是错误的
class B{
   
public:
    int a;
    C n;
    B():a(1){
   } // 报错  error: no matching function for call to 'C::C()'
};

总结

  • 构造一个对象一定会自动调用一个构造函数
  • 如果一个类中没有实现默认构造函数,编译器会自动生成一个(前提是没有实现任何带参数的构造函数)
  • 如果一个类中实现了带参数的构造函数,一定要实现一个无参的构造函数。因为如果在构造对象时不带参数将无法找到无参的构造函数导致编译失败
  • 构造函数可以有多个,根据构造对象时所传递的参数,会自动调用对应的构造函数
  • 类不会占用程序的内存空间,对象才会占用程序的内存空间
class B{
   }

B b(); //不是实例化 一个返回class的b函数的声明
B (1,2);//是实例化

析构函数

定义

用于清理对象
语法:~ClassName()

  • 析构函数没有参数 没有任何返回类型的声明
  • 析构函数只有一个 不可以重载
  • 析构函数在对象销毁的时候自动调用
class Cat{
   
public:
    int age,id;
    char name[32];
    Cat(){
   
        std::cout<<"cat"<<std::endl;
    }
    ~Cat(){
   
        std::cout<<"~~"<<std::endl;
    }
};

// return 0;代表程序结束 也就是class要销毁了

作用

在栈空间的变量 函数等系统会自动销毁
而在堆空间我们申请的空间系统不会自动销毁,要使用析构函数进行销毁

class Cat{
   
public:
    int age,id;
    char *name;
    Cat(){
   
        std::cout<<"Cat()"<<std::endl;
        name=(char*) malloc(32); // 32B的空间 分配在堆上
    }
    Cat(char *name1){
   
        std::cout<<" Cat(char *name1)"<<std::endl;
        int len = strlen(name1);
        //分配在堆上
        name=(char *) malloc(len+1);
        strcpy(name,name1);
    }
    ~Cat(){
   
        std::cout<<"~~"<<std::endl;
        if (name!=NULL){
   
            free(name);
        }
    }
};

多个对象构造和析构

当类中的成员变量是另外一个类的实例化对象,成员对象

成员变量所属的类中没有实现无参构造函数的时候,用初始化成员列表

class ABC{
   
public:
    ABC(int x,int y,int z){
   
        std::cout<<"ABC(int x,int y,int z)"<<std::endl;
    }
    ~ABC(){
   
        std::cout<<"~ABC()"<<std::endl;
    }
};

class MyD{
   
public:
    MyD(): abc1(1,2,3), abc2(1,1,1){
   
        std::cout<<"MyD()"<<std::endl;
    }
    ~MyD(){
   
        std::cout<<"~MyD()"<<std::endl;
    }
    ABC abc1,abc2;
};

/*
当实例化MyD时,会先构造成员对象(按照定义顺序),然后再构造MyD自身
当销毁的时候,先销毁MyD 然后再销毁成员对象(与定义顺序相反)
*/
MyD y;// 先输出两个ABC(int x,int y,int z) 分别时abc1 abc2的 然后输出MyD()
      // 销毁的时候  ~MyD()  然后两个~ABC() 分别时abc2 abc1的

也就是说
内部准备好 外部才可以准备(构造过程,内部按定义准备
外部销毁了 内部才可以销毁(析构过程,内部反定义销毁

对象的动态建立和释放

在c语言中使用malloc和free
在c++中一般使用 new delete

new和delete

基本语法使用

new

//new动态分配堆内存
使用形式:指针常量 = new 类型(常量);
		 指针常量 = new 类型[表达式];
作用:从堆分配一块类型大小的存储空间,返回首地址
其中:常量是初始化的值可省略
	 创建数组对象时,不能为对象指定初始值 


// 例子
//在堆内存申请一个int(4b)大小的空间,并初始化为10
int *p = new int(10);

//在堆内存申请四个int(4*4b)大小的空间
int *q = new int[4];

// 在堆上申请一个Box类型大小的空间,会进行实例化对象
Box *x = new Box;

Box *y = new Box(10,10,10);

在这里插入图片描述

delete

防止内存泄漏

delete 释放已分配的堆内存空间
使用形式:delete 指针变量;
		 delete[] 指针变量;

其中:指针变量 必须是new返回的指针	


delete x;
delete[] xs;

new delete和malloc free区别

c++为了兼容c保留了malloc和free,但是不建议使用。
注意:new和delete是运算符,不是函数,因此执行效率更高

  • malloc/free是c的标准库的函数,new/delete是c++的运算符。调用函数要有函数调用栈 有消耗比运算符效率低

  • new能自动计算需要分配的内存空间,而malloc需要手动计算字节数

    • int *p = new int[4]; int *q = (int *)malloc(4*sizeof(int));
  • new和delete直接带具体类型的指针,而malloc和free返回void类型的指针(强制类型转化)

    • int *p = new int[4]; int *q = (int *)malloc(4*4); malloc 要强制类型转化
  • new类型是安全的,malloc不是。

  • new 调用构造函数 而malloc不能 malloc可能给一些无法初始化

  • delete 调用析构函数 而free不能 free可能导致内存泄露

区别在以下几个方面

  • 效率 运算符 函数
  • 计算空间 自动计算 手动计算
  • 类型 具体类型指针 void类型指针
  • 安全 类型安全 类型不安全
  • 是否能调用构造 析构函数

对象的赋值

利用实例化好的对象对另外一个对象初始化

会存在一些问题!! 如果类里面成员变量有指针会出现问题,同一指针指向同一空间
如下代码和图片

class Test{
   
public:
    int x,y;
    int *sum;
    Test(){
   
        std::cout<<"Test()"<<std::endl;
        x=0;
        y=0;
        sum = new int[4];
    }
    Test(int a,int b):x(a),y(b){
   
        std::cout<<"Test(int a,int b)"<<std::endl;
        sum = new int[4];
    }
};


    Test *t1 = new Test(10,10);
    Test t2 = *t1;

    std::cout<<t1->x<<std::endl;
    std::cout<<t2.x<<std::endl;

    for (int i =0;i<4;++i){
   
        t1->sum[i]=i;
    }
    std::cout<<t2.sum[0]<<std::endl;

    // 手动销毁t1开辟的堆空间-- 意味着销毁*t1这个对象 自动调用析构函数 顺带手动释放成员变量申请的堆地址
    delete t1;

    //此时再访问t2的sum,数据会乱 因为指向了被回收的空间 
    // 除此t1销毁的时候释放sum空间  但在t2销毁的时候又释放一次sum,造成了同一块堆空间的二次释放  第二次释放的时候会出现异常 后续的代码就不会正常执行
    std::cout<<t2.sum[0]<<std::endl;

在这里插入图片描述
那么怎么解决上述问题?

一种方法:手动

Test t2;
t2.x=t1.x;
t2.y=t1.y;
for (int i=0;i<4;++i)
	t2.sum[i]=t1.sum[i];

另外方法:拷贝构造函数

拷贝构造函数

当使用Test t2=t1的时候,不会调用构造函数,而是调用拷贝构造函数(如果我们没有写,编译器自动生成一个拷贝构造函数)

class Test{
   
public:
    int x,y;
    int *sum;
...
    // 拷贝构造函数
    Test(const Test &t){
    // 参数是引用的右值 此例子中也就是t1的引用
        std::cout<<"Test(const Test &t)"<< std::endl;
        /*
         * 编译器自动帮我们实现的是:
         *  x=t.x;
         *  y=t.y;
         *  sum=t.sum;
         * */
        x = t.x;
        y = t.y;
        sum=new int[4]; // 新开辟一段空间
        // 将t1.sum空间的内容拷贝到sum所指向的空间
        for(auto i=0;i<4;++i)
            sum[i]=t.sum[i];

    }
};

    Test t1(10,10);
    t1.sum[0]=100;
    t1.sum[1]=101;
    t1.sum[2]=102;
    t1.sum[3]=103;

	Test t2=t1;

	//地址不同
	std::cout<<t1.sum<<std::endl;
	std::cout<<t2.sum<<std::endl;

1、为什么拷贝构造函数的形参是引用?Test(const Test &t)

因为如果是这样的话Test(const Test t),也就是会调用一下Test t = t1这样会触发拷贝构造函数,会造成无限循环调用拷贝构造函数,所以必须是引用。
除此 还会新开辟空间生成t1 造成空间浪费

2、为什么拷贝构造函数的形参是必须用const修饰?Test(const Test &t)

保护右值,即防止t1对象被修改

浅拷贝和深拷贝

浅拷贝

同一类型的对象之间可以赋值,使得两个对象的成员变量的值相同,两个对象依然是独立的两个对象,这种情况叫做浅拷贝

一般情况,浅拷贝没有任何副作用,但是当成员变量有指针,并且指针指向动态分配的内存空间,这样的浅拷贝会导致两个对象的指针指向同一空间,当两个对象销毁的时候会调用析构函数,造成同一空间被释放两次从而导致程序运行出错。

如果我们没有实现拷贝构造函数,c++编译器会自动实现一个拷贝构造函数,是默认拷贝构造函数 是浅拷贝。
在这里插入图片描述

深拷贝

自己手动实现拷贝构造函数,在拷贝构造函数中需要对对象中的指针变量进行单独的内存申请。
两个对象中的指针变量不会指向同一块内存空间,然后再将右值对象指针所指向的空间中的内容拷贝到新的对象指针所指向的堆空间中。

在这里插入图片描述

引用作为形参

好处

中间没有临时对象的产生,提高了程序执行的效率。

注意事项

防止值被修改,也就是为了保护传入的值,应该const修饰 使其为只读

面向对象内存模型

编译器对属性和方法的处理机制

在c语言中,数据和处理数据的操作(函数)是分开声明的,也就是说,语言本身并没有数据和函数的关联性。
在c++中,通过抽象数据类型,在类中定义数据和函数,实现数据和函数直接的绑定

class C1{
   
public:
    int x;
    int y;
    int k;
protected:
private:
};
class C2{
   
public:
    int x,y,k;
    int getK(){
   return k;}
    void setL(int mK){
   k=mK;}
};

    C1 c1;
    C2 c2;
    std::cout<<"c1:"<<sizeof(c1)<< std::endl; //12
    std::cout<<"c2:"<<sizeof(c2)<< std::endl; //12

上面的代码 输出的c1和c2的占用空间大小竟然是相同的!!

也就是说,C++类对象中的成员变量和成员函数是分开存储的

成员变量

  • 普通成员变量:存储在对象中(栈),与struct变量有相同的内存布局和字节对齐方式
  • 静态成员变量:存储在全局数据区

成员函数

  • 存储在代码段

内存从上到下可以大致分为:栈区 堆区 数据段 代码段

this指针

所有类实例化的对象共享处于代码段中的成员函数,但是成员函数怎么直到是谁调用的它呢?这就引出了this指针

this指针就是指向调用该成员函数的对象

gdb调试

    C1 c1;
    c1.k=10;
    C2 c2;
    c2.k=100;
    c2.getK();

1、g++ -g main.cpp -o main
2、gbd main
3、start n(next)print c2.getK quit(退出)

在这里插入图片描述
在这里插入图片描述

这里的参数就是this指针 编译器自动生成的,this指针指向调用方法的对象

c.getK();getK(&c);  传入对象c的地址,传给了C *const this(成员函数的第一个形参)  this指向c的地址

静态成员属性和方法

静态成员属性

  • 关键字 static 声明一个类的成员

  • 静态成员提供一个同类对象的共享机制

  • 这个类无论有多少个对象被创建,这些对象共享这个static成员

  • static的作用域是类和对象之间,但不是对象的成员

  • 调用

    • 对象.静态成员属性
    • 类::静态成员属性 (推荐,可读性强)
class Sheep{
   
public:
    char name[55];
    int age;
    static int cnt; //这里只是声明 定义和初始化要放到全局
    Sheep(){
   
        cnt++;
    }

    ~Sheep(){
   
        cnt--;
    }
};
int Sheep::cnt;//定义sheep类的静态成员变量cnt 并且初始化(如果不初始化默认为0)

// main函数
    Sheep *p = new Sheep[10];

    //访问静态成员变量
    std::cout<<Sheep::cnt<<std::endl;
    std::cout<<p->cnt<<std::endl;

静态成员函数

  • 使用static修饰的成员函数

  • 在静态成员函数内不能访问除静态成员变量以外的其他成员变量(只能访问静态成员变量

    • 因为静态成员函数没有this指针,它不是类的成员
  • 调用方法

    • 对象.静态成员函数()
    • 类::静态成员函数() (推荐,可读性强)
class Sheep{
   
public:
    char name[55];
    int age;
    static int cnt; //这里只是声明 定义和初始化要放到全局
    //静态成员函数
    static int sheepNum(){
   
        //std::cout<<age;//报错,静态成员函数不能访问非静态的成员变量
        return cnt; //静态成员属性
    }

    Sheep(){
   
        cnt++;
    }

    ~Sheep(){
   
        cnt--;
    }
};

int Sheep::cnt=0;//定义sheep类的静态成员变量cnt 并且初始化(如果不初始化默认为0)
// main函数中
    Sheep s1;
    std::cout<<Sheep::sheepNum();

存在的意义?

  • 如果静态成员属性是private 那么只能通过静态成员函数访问
  • 可以实现某些特殊的设计模式,如singleton(单例模式:一个类只能有一个对象)
  • 可以封装某些算法,比如数学函数(in sin cos tan 等等),这些函数本就么必要属于任何一个对象,所有直接从类上调用感觉更好

string类

简述

c语言中使用字符数组表示字符串的,这样效率比较低
C++标准库力通过 类string 重新自定义了字符串

#include <string>

  • 支持直接字符串连接
  • 支持直接字符串大小比较
  • 支持直接字串查找和提取
  • 支持直接字符串的插入和替换
  • 具备字符串数组的灵活性,可以通过下标来访问每个字符

常见构造方法

    std::string s;//空字符串
    std::string s1("hello"); //用字符串常量构造string对象  调用的构造函数 string(const char*)
    std::string s2(4,'k');//kkkk
    std::string s3("123456",1,3);//从str下标为pos的开始去n个字符  从"123456" 下标为1的2数3个字符
    
    std::cout<<s.size()<<std::endl; //字符串长度
    std::cout<<s.length()<<std::endl; //字符串长度


//下面是错误的
std::string s4('A'); //传入一个char类型
std::string s5(123); //传入一个int类型
std::string s6(123.2365); //传入一个double类型

赋值

1、可以使用char*类型的变量、常量,char类型的常量对string进行赋值

    std::string s;

    s = "hello";

    s='A'; //赋值的是字符A 但是s是字符串

    char name[32];
    strcpy(name,"du");
    s=name;

2、assign成员函数

    std::string s1("string"),s2;
    s2.assign(s1);//s2=s1
	s2.assign("ddddd");
	s2.assign(s1,1,3);//s2=s1下标从1开始 取3个
	s2.assign(3,'a');//aaa

拼接

// 直接使用 + 
	std::string s1("+"),s2;
    s2 = '1'+s1+'2'; //1+2
	
	to_string(val); //将int double bool转化为string

// append成员函数,返回对象自身的引用
	s2.append(s1); // 将s1追加到s2后面 s2=s2+s1
	s2.append(s1,1,3); //下标 1  数量 3(追加字串一般用append)
	s2.append(3,'k'); // 追加3个k

比较大小

1、直接使用比较的运算符比较大小(基于ascii比较)

string s1("hello"),s2("world");
bool ret = s1>s2;

2、compare成员函数

  • 小于0 表示当前的字符串小于传入的的字符串
  • 等于0 表示当前的字符串等于传入的的字符串
  • 大于0 表示当前的字符串等于传入的的字符串

主要是用于指定字符串的子串进行比较大小

s1.compare(1,3,s2,1,3);  //s1从下标1开始数3个字符的字串与s2从下标1开始数3个字符的字串比较

求字串

substr成员函数可以用于求子串,函数原型:

string substr(int n=0,int m=string::npos) const;

	string s1 = s.substr(0, 2);//从s的索引为0的位置截取2个长度的字符串

	string s2 = s.substr(2, 3);//从s的索引为2的位置截取3个长度的字符串

	string s3 = s.substr(2);//如果只提供一个索引位置信息,那么截取s索引为2到字符串尾部

	string s4 = s.substr();//如果不提供任何信息,那么等价于s3=s

	string s5 = s.substr(7);//按理s的索引最多到6,但是组成字符串默认最后一位为空字符,所以索引可以到7

常用函数

查找子串和字符

查不到返回string::npos,是string定义的一个静态常量

  • find:从前往后查找字串或字符出现的位置 *(比较重要)
  • rfind:从后往前查找字串或字符出现的位置
  • find_first_of:从前往后查找何处第一次出现另一个字符串中包含的字符
  • find_last_of:从后往前查找何处第一次出现另一个字符串中包含的字符
  • find_first_not_of:从前往后查找何处第一次出现另一个字符串中没有包含的字符
  • find_last_not_of:从后往前查找何处第一次出现另一个字符串中没有包含的字符

替换子串

查不到返回string::npos,是string定义的一个静态常量

replace成员函数可以对string对象中的字串进行替换,返回对象自身的引用

replace(int pos,int n,string s,[int pos],[int n]);

//c++ 对单引号和双引号区分特别严重
string s("hello world");
s.replace(1,4,"i"); //hi world
s.replace(1,1,2,'i'); //hii world
s.replace(1,2,"abcdefghijk",8,1);//hi world

删除子串

erase删除string对象的子串,返回对象自身的引用

string s("real dog");
s.erase(0,4);// dog   删除从下标0开始 4个字符
s.erase(2);// d       删除从下标2开始到结尾的全部字符


// 删除字符串中的steel
    // 删除字符串中的steel
    std::string s("sdjaldsteeldsakdssteel"),moom("steel");
    while (s.find(moom)!=std::string::npos){
   
        s.erase(s.find(moom),moom.length());
    }
    std::cout<<s<<std::endl;

插入字符串

insert 成员函数可以在 string 对象中插入另一个字符串,返回值为对象自身的引用

string s("limit");
s.insert(2,"123");//li123mit 在1-2之间插入
s.insert(1,5,'x');//lxxxxxi123mit 在0-1之间插入5个x
s.insert(3,"hello",1,3);// 在2-3之间插入hello 1到1之后的3个 子串

动态数组设计

Array.h

//
// Created by DELL on 2024-01-04.
//

#ifndef ARRAY_ARRAY_H
#define ARRAY_ARRAY_H
#define DefaultLen 4

using u32_t = unsigned int;
class Array {
   
private:
    int *data;
    u32_t length; //数组当前长度
    u32_t capacity;//数组容量
    static int indexMax;
    //自动扩容函数
    void extend();
public:
    //默认构造函数
    Array();
    //带参数的构造函数
    Array(u32_t len);
    //拷贝构造函数
    Array(const Array &arr);
    //析构函数
    ~Array();

    //获得数组当前长度
    u32_t getLength();

    //获取数组容量
    u32_t getCapacity();

    //返回指定位置的值
    int getValue(u32_t index);

    //返回指定值的下标
    int getIndex(int value);

    //销毁动态数组
    void destroy();

    //在指定位置插入元素
    bool insert(u32_t i,int value);

    //删除指定位置元素
    void remove_index(u32_t i);

    //删除指定值的元素
    void remove_value(int value);
};


#endif //ARRAY_ARRAY_H

Array.cpp

//
// Created by DELL on 2024-01-04.
//
#include <iostream>
#include "Array.h"
#include "string"
int Array::indexMax = 999999;
using namespace std;
Array::Array() {
   
    this->data=new int[DefaultLen];//默认分配4个int类型的空间
    this->length=0; //当前长度为0
    this->capacity=DefaultLen;//容量为DefaultLen
}

Array::Array(u32_t len){
   
    this->data=new int[len];
    this->length=0;
    this->capacity=len;
}

//拷贝构造函数 实现深拷贝
Array::Array(const Array &arr) {
   
    this->data=new int [arr.capacity];
    this->length = arr.length;
    this->capacity = arr.capacity;
    for (int i = 0; i < length; ++i) {
   
        this->data[i]=arr.data[i];
    }
}

Array::~Array() {
   
    if(this->data!= nullptr){
   
        delete[] this->data;
    }
}

void Array::extend() {
   
    //临时指针保存堆空间的首地址
    int * tmp = data;
    data = new int [2*capacity];
    for (int i = 0; i < length; ++i) {
   
        data[i] = tmp[i];
    }
    delete[] tmp;
    capacity *=2;
}

void Array::destroy() {
   
    if (data){
   
        delete[] data;
        data= nullptr; // 这里将data指针置空,防止析构函数二次销毁
        length=0;
        capacity=0;
    }
}

u32_t Array::getLength() {
   
    return length;
}

u32_t Array::getCapacity() {
   
    return capacity;
}

int Array::getValue(u32_t index) {
   
    if(index<length) return data[index];
    cout<<"index out of range"<<endl;
    return indexMax;
}

bool Array::insert(u32_t i, int value) {
   
    //i 和 capacity 的大小关系

    if(length==capacity)extend();

    if(i>=capacity){
   
        data[length] = value;
    }
    else{
   
        int j=length;
        for (; j > i; --j) {
   
            data[j] = data[j-1];
        }
        data[j]=value;
    }
    length++;
    return true;
}

int Array::getIndex(int value) {
   
    for (int i = 0; i < length; ++i) {
   
        if (data[i]==value)return i;
    }
    cout<<"not find value:"<<value<<endl;
    return -1;
}

void Array::remove_index(u32_t i) {
   
    if(i>=length){
   
        cout<<"index out of range"<<endl;
        return;
    }
    for (int j = i; j < length; ++j) {
   
        data[j] = data[j+1];
    }
    length--;
}

void Array::remove_value(int value) {
   
    int pos = getIndex(value);
    while (pos>=0){
   
        remove_index(pos);
        pos= getIndex(value);
    }
}


main.cpp

#include <iostream>
#include "Array.h"
using namespace std;
/*
 * Array动态数组设计,功能(存储整型数据)
 * 1.自动扩容
 * 2.销毁数组
 * 3.在指定位置插入元素
 * 4.删除指定位置的元素
 * 5.删除指定值的元素
 * */

int main() {
   
    Array t(5);
    t.insert(0,0);
    t.insert(1,1);
    t.insert(2,2);
    t.insert(4,3);
    t.insert(4,4);
    t.insert(500,5);
    t.insert(8,0);

//    t.remove_index(20);
//    t.remove_index(2);
	t.remove_value(0);
//    t.getValue(100);
//    t.destroy();
    for (int i = 0; i < t.getLength(); ++i) {
   
        cout<<t.getValue(i)<<ends;
    }
    cout<<endl;
    cout<<t.getLength()<<endl;
    cout<<t.getCapacity()<<endl;
    return 0;
}

相关推荐

  1. C++ 对象

    2024-01-05 16:56:01       9 阅读
  2. C++:对象(2)

    2024-01-05 16:56:01       29 阅读
  3. C++对象(中)

    2024-01-05 16:56:01       42 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-05 16:56:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-05 16:56:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-05 16:56:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-05 16:56:01       20 阅读

热门阅读

  1. LLM之Agent再探

    2024-01-05 16:56:01       40 阅读
  2. Flink学习-处理函数

    2024-01-05 16:56:01       38 阅读
  3. argparse学习使用

    2024-01-05 16:56:01       44 阅读
  4. Elasticsearch 优化

    2024-01-05 16:56:01       34 阅读
  5. 计算机网络问题

    2024-01-05 16:56:01       31 阅读
  6. MySQL 存储引擎对比:InnoDB vs. MyISAM

    2024-01-05 16:56:01       36 阅读