C++基础学习笔记----第九课(构造函数)

时间:2022-04-10 21:50:51

本节主要讲对象的初始化、构造函数的基本概念使用方法以及两个特殊的构造函数,课后练习是手写数组类。

对象的初始化

所有的对象都需要一个确定的初始状态。这样我们可以使用一个initialize函数来进行初始化。对象在建立后可以调用这个函数进行初始化。

initialize在这里只是一个普通的函数,必须进行调用,如果没有调用,对象的结果将是不确定的。

#include <stdio.h>
class A
{
private:
int a;
int b;
int c;

public:
void initialize()
{
a = 9;
b = 8;
c = 7;
}
int print()
{
printf ("a = %d\n", a);
printf ("b = %d\n", b);
printf ("c = %d\n", c);

return 0;
}
};

int main()
{
A b;

b.print();

return 0;
}
这里打印的三个值在VS2008的编译环境下是一样的,但是很明显都是垃圾值。

initialize这个函数只是C++中的一种初始化函数,个人感觉在类的封装以后应该没有使用这种形式的初始化了,因为要额外的调度一次初始化函数。

构造函数

基本使用方法

构造函数的特点:

构造函数是类成员函数,比较特殊的类成员函数

构造函数的名字与类的名字相同

构造函数在定义的时候可以有参数,但是没有任何返回类型的声明

对象的内存分配并不是由构造函数来完成的,对象的内存分配是由编译器来完成的,构造函数的作用是对对象本身进行初始化工作,也就是给用户提供初始化类中成员变量的一种方式。

基本例程如下:

#include <stdio.h>
class A
{
private:
int a;
int b;
int c;

public:
A(int d)
{
a = b = c = d;
}
int print()
{
printf ("a = %d, b = %d, c = %d\n",a, b, c);
return 0;
}
};

int main()
{
A b(1);
A c = 2;

b.print();
c.print();
}
这里构造函数初始化类的成员一共有两种形式,分别是 A b(1); A c =2;这两种形式都是自动调用构造函数。还有一种是手动调用构造函数的形式,
A b = A(9);

成员函数的重载

类的成员函数和普通函数一样可以进行重载,并遵守相同的重载规则。

#include <stdio.h>
class A
{
private:
int a;
int b;
int c;

public:
A(int d)
{
a = b = c = d;
}
A()
{
a = b = c = 8;
}
int print()
{
printf ("a = %d, b = %d, c = %d\n",a, b, c);
return 0;
}
void print(int x)
{
printf("x = %d", x);
}
};

int main()
{
A b = A(9);

b.print();

return 0;
}


拷贝构造函数和无参构造函数

无参构造函数是构造函数没有参数,而拷贝构造函数是为了无参构造函数而生的。

例程如下:

#include <stdio.h>
class A
{
private:
int a;
int b;
int c;

public:
A()
{
printf (".....\n");
}

A (const A& i)
{
printf ("~~~~~\n");
}
void print(int x)
{
printf("x = %d", x);
}
};

int main()
{
A b1 ;

A b2 = b1;

return 0;
}
程序打印结果如下图所示:

C++基础学习笔记----第九课(构造函数)
这里的b1对象没有进行初始化,这个时候无参的构造函数就默认对没有进行初始化的对象进行初始化,而b2对象被b1赋值,相当于A b2 = 常量,这样b2所对应的构造函数就应该是一个有参的重载构造函数,也就是A (const A& i)

当类中没有定义任何一个构造函数,C++编译器会为其提供无参构造函数和拷贝构造函数。当类中定义了任意的非拷贝构造函数的时候,C++编译器不会为其提供无参构造函数。

关于拷贝构造函数的疑惑

拷贝构造函数就是对构造函数的一个复制,那么A (const A& i)这个函数中的参数i就是新的类似类的对象,我们可以通过i直接调用原来的构造函数中的成员。基本例程如下:
A()
{
int a;
printf (".....\n");
}

A (const A& i)
{
i.a = 2;
printf ("~~~~~\n");
}
上面的程序编译将会报错,因为变量a的作用域只在原函数的A()中,所以我们不能够进行调用,但是我们可以对A函数中的printf进行调用。具体的拷贝构造函数还是不太理解,而且有很多其他的要求,例如深拷贝和浅拷贝,还有继承类中的拷贝等等。

参考其他博客得到:

如果一个类中没有定义任何的构造函数,那么编译器只有在以下三种情况,才会提供默认的构造函数:
1、如果类有虚拟成员函数或者虚拟继承父类(即有虚拟基类)时;
2、如果类的基类有构造函数(可以是用户定义的构造函数,或编译器提供的默认构造函数);
3、在类中的所有非静态的对象数据成员,它们对应的类中有构造函数(可以是用户定义的构造函数,或编译器提供的默认构造函数)。

#include <stdio.h>
class A
{
private:
int a;
int b;
int c;

public:
/*A()
{
//a = b = c = i;
}
A(const A& oj)
{

}*/

void print()
{
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
}
};

int main()
{
A b;
A c = b;
b.print();
c.print();

return 0;
}

课后练习

要求:创建一个数组类。

具体代码如下所示:

main.cpp

#include <stdio.h>
#include "a.h"

int main()
{
A a = 5;

for (int i = 0; i < a.getlength(); i++)
{
a.setdata(i,i);
printf ("%d\n",a.getdata(i));
}

A b = a;
for (int j = 0; j < b.getlength(); j++)
{
printf ("%d\n",b.getdata(j));
}

a.destroy();
b.destroy();
return 0;
}
a.cpp

#include "a.h"

A::A(int length)
{
if (length < 0) //进行安全性检测
{
alength = 0;
}
alength = length;

/*申请大小为alength个int类型的空间*/
ap = new int[alength];
}

/*这里不使用C++编译器给的默认的拷贝构造函数*/
A::A(const A& aj)
{
alength = aj.alength;

//重新从堆上进行空间分配
ap = new int[alength];

/*依次进行值得拷贝*/
for(int i=0; i<alength; i++)
{
ap[i] = aj.ap[i];
}
}

int A::getlength()
{
return alength; //直接返回数组的长度
}

void A::setdata(int index, int data)
{
ap[index] = data;
}

int A::getdata (int index)
{
return ap[index];
}

void A::destroy()
{
alength = -1; //将长度赋为-1,表示出错
delete []ap; //对申请的堆空间进行释放
}

a.h

#ifndef _A_H
#define _A_H

class A
{
private:
int alength;
int* ap;

public:
A(int length);
A(const A& aj);
int getlength();
void setdata(int index,int data);
int getdata (int index);
void destroy();
};

#endif
通过上面的a.cpp可以看出,这里没有使用默认的 C++拷贝构造函数,因为默认的C++拷贝构造函数只是进行简单的值复制,也就是将我们在 构造函数中的数组的大小数组的指针进行复制经过拷贝构造函数的数组和原来的数组指向同一块内存空间,而我们在主函数中对同一块内存空间进行了两次delete,所以程序会崩溃。 解决办法是不使C++编译器默认的拷贝构造函数,在自己定义的拷贝构造函数中重新为第二个数组分配内存空间。