一:案例分析
(本案例来自黑马程序员老师的课)
案例描述: 实现一个通用的数组类,要求如下:
- 可以对内置数据类型以及自定义数据类型的数据进行存储
- 将数组中的数据存储到堆区,并用一个未明确的T类型的指针指向这个动态数组。
- 构造函数中定义可以传入数组的容量
- 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
- 提供尾插法和尾删法对数组中的数据进行增加和删除
- 可以通过下标的方式访问数组中的元素(类模板下的函数重载)
- 可以获取数组中当前元素个数和数组的容量(编写两个get函数即可)
文件组织:
二:实际代码
1.My_array.hpp中的代码:
#pragma once //防止重复编译,微软系编译器独有的
#include<iostream>
using namespace std;
//数组模板,用来接受传入的类型
template<class T>
class My_array {
int m_size;//数组中实际存储的元素个数
int m_capacity;//数组的最大容量
T* pAddress;//根据传入类型定义了一个指针类型,待会儿会在构造函数中在堆区创建一个数组并用这个指针指向那块内存
public:
My_array(int capacity);
int Get_size();
int Get_capacity();
void Push_Back(const T& val);//尾插法
void Pop_Back();//尾删法
My_array(const My_array& array);//拷贝构造函数,注意深浅拷贝,这里有指针类型,必须深拷贝
~My_array();
My_array& operator = (const My_array & array) //操作符重载必须是成员函数
{
this->m_size = array.m_size;
this->m_capacity = array.m_capacity;
this->pAddress = new T[this->m_capacity];
for (int i = 0; i < this->m_size; i++) {
this->pAddress[i] = array.pAddress[i];
}
}
T& operator[](int index){ //重载[]操作符
return this->pAddress[index];
}
};
//构造函数类外实现
template<class T>
My_array<T>::My_array(int capacity) {
m_capacity = capacity;
m_size = 0;
pAddress = new T[this->m_capacity];
}
//m_size接口
template<class T>
int My_array<T>::Get_size() {
return m_size;
}
//m_capacity接口
template<class T>
int My_array<T>::Get_capacity() {
return m_capacity;
}
//拷贝构造函数
template<class T>
My_array<T>::My_array(const My_array& array) {
if (this->pAddress != 0) {
delete[]pAddress;
this->m_size = 0;
this->m_capacity = 0;
}
this->m_size = array.m_size;
this->m_capacity = array.m_capacity;
this->pAddress = new T[this->m_capacity];
for (int i = 0; i < this->m_size; i++) {
this->pAddress[i] = array.pAddress[i];
}
//这里千万不能写返回值,return *this;
//因为构造函数也好,拷贝构造函数也好,都不能有返回值,原因下面讲
}
//尾插法
template<class T>
void My_array<T>::Push_Back(const T&val) {
if (this->m_size == this->m_capacity) {
cout << "数组已满,无法插入" << endl;
return;
}
else
this->pAddress[this->m_size] = val;
this->m_size++;
}
//尾删法
template<class T>
void My_array<T>::Pop_Back() {
if (this->m_size == 0) {
cout << "数组已删除完毕" << endl;
return;
}
else
this->m_size--;//逻辑上的删除,并未真的删除
}
//析构函数
template<class T>
My_array<T>::~My_array() {
if (this->pAddress != 0) {
delete[]this->pAddress;
pAddress = NULL;//指针只想空,防止野指针
this->m_size = 0;
this->m_capacity = 0;
}
}
2.main.cpp中的代码:
#include<iostream>
#include<string>
#include"My_array.hpp"
using namespace std;
//自定义一个类型,custom表示自定义的意思
class custom {
public:
string name;
string age;
custom() {};
custom(string name, string age) {
this->age = age;
this->name = name;
}
};
//类模板作函数参数
void print_int_array(My_array<int>&array) {
for (int i = 0; i < array.Get_size(); i++) {
cout << array[i]<<" ";
}
cout << endl;
}
void test_int() {
My_array<int>array(4);
for (int i = 0; i < 4; i++) {
array.Push_Back(i);//测试尾插法
}
print_int_array(array);
array.Pop_Back();//测试尾删法
print_int_array(array);
My_array<int>array1(array);//测试拷贝构造函数
print_int_array(array1);
}
void print_custom_array(My_array<custom>&array) {
for (int i = 0; i < array.Get_size(); i++) {
cout << array[i].name << " " << array[i].age << " ";
}
cout << endl;
}
void test_custom() {
My_array<custom>array(3);
custom c1("李四", "20");
custom c2("王五", "15");
custom c3("法外狂徒—张三", "10");
array.Push_Back(c1);//测试尾插法
array.Push_Back(c2);
array.Push_Back(c3);
print_custom_array(array);
array.Pop_Back();//测试尾删法
print_custom_array(array);
My_array<custom>array1(array);//测试拷贝构造函数
print_custom_array(array1);
}
int main()
{
test_int();
test_custom();
return 0;
}
二:重要知识点
1.返回值问题
Q:为什么构造函数(拷贝构造函数)无法返回值?
首先是规定,C++标准规定了构造/析构/自定义类型转换符不可以指定返回类型。我个人的一个理解是构造函数是用来告诉编译器,我想定义了一个对象,你编译器得给我在栈或堆中分配内存,不然我往哪放?这个过程就相当于int a;没那个必要返回值。
2.继承过程中的构造函数问题
问题代码://会发现,当子类和父类都含有有参构造函数之后就会报错,编译器给出的错误原因是父类没有默认构造函数,这就很令人迷惑了。
#include<iostream>
using namespace std;
class base {
public:
base(int a){}
};
class derive :public base {
public:
int a;
derive(int a) {
this->a = a;
}
};
int main() {
derive d(1);
}
构造原则如下:
1. 如果子类没有定义构造方法,则调用父类的无参数的构造方法。
2. 如果子类定义了构造方法,不论是无参数还是带参数,在创建子类的对象的时候,首先执行父类无参数的构造方法,然后执行自己的构造方法。 //这就能解释上面的问题
3. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数,则会调用父类的默认无参构造函数。
derive(int a) :base(a){} //这就属于显示的调用父类的构造函数
4. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数。
5. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造方法,则子类必须显示调用此带参构造方法)。
6. 如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式