记录:构造函数、构造函数的分类、为什么需要构造和析构函数、拷贝构造函数的调用时机、拷贝构造函数的调用时机、对象的初始化列表、组合对象的执行顺序、对象的动态建立和释放
构造函数
有关构造函数
1构造函数定义及调用
1)C++中的类可以定义与类名相同的特殊成员函数,这种与类名相同的成员函数叫做构造函数;
2)构造函数在定义时可以有参数;
3)没有任何返回类型的声明。
2构造函数的调用
自动调用:一般情况下C++编译器会自动调用构造函数
手动调用:在一些情况下则需要手工调用构造函数
代码:
class Test
{
public:
Test()
{
cout << "构造函数被调用" << endl;
}
~Test()
{
cout << "析构函数被调用" << endl;
}
};
int main()
{
Test t1;
Test t2;
system("pause");
return 1;
}
构造函数的作用:
class Test
{
public:
Test()
{
a = 10;
p = (char *)malloc(100);
strcpy(p, "aaaaffff");
cout << "构造函数被调用" << endl;
}
void pintP()
{
cout << p << endl;
cout << a << endl;
}
~Test()
{
if (p != NULL)
{
free(p);
}
cout << "析构函数被调用" << endl;
}
private:
int a;
char *p;
};
int main()
{
Test t1;
t1.pintP();
Test t2;
t2.pintP();
system("pause");
return 1;
}
构造函数的分类
有参数构造、无参构造、赋值构造函数
有参数构造调用有参构造函数有三种方法,括号法 ,等号法(逗号表达式方式),直接调用构造构造函数法
class Test
{
public:
Test()
{
m_a = 0;
m_b = 0;
cout << "这是wu参构造函数" << endl;
}
Test(int a, int b)
{
m_a = a;
m_b = b;
cout <<a<<b<< "这是有参构造函数" << endl;
}
Test(const Test& obj)
{
cout << "我也是构造函数" << endl;
}
private:
int m_a, m_b;
};
int main()
{
Test t1(1, 2); //括号法,
Test t2 = Test(1, 3);
system("pause");
return 1;
}
对象的初始化和对象的赋值
Test t2 = Test(1, 3); //t2对象的初始化,对象的初始化和对象的赋值是两个不同的概念。
**
为什么需要构造和析构函数
1、造函数的调用方法是,隐式调用:自动调用。
代码:
class Test
{
public:
void init(int a, int b)
{
m_a = a;
m_b = b;
cout << a << b << "这是有参构造函数" << endl;
}
private:
int m_a, m_b;
};
int main()
{
Test t1;
t1.init(20, 30);
Test tArray[3]; //这种场景下很蹩脚,但是
tArray[0].init(1, 2);
tArray[1].init(1, 2);
tArray[2].init(1, 2);
//但是如果,tArray【200】在这种场景之下,满足不了需求,所以需要初始化。
// Test tArray[3];
system("pause");
return 1;
}
拷贝构造函数的调用时机
1、第一第二种方法,初始化系统拷贝构造函数。
用对象初始化另一个对象的时候调用拷贝构造函数
class Test
{
public:
Test(){}
Test(int a, int b)
{
m_a = a;
m_b = b;
cout << a << b << "这是有参构造函数" << endl;
}
Test(const Test& obj)
{
m_a = obj.m_a + 100;
m_b = obj.m_b + 100;
cout << "拷贝构造函数" << endl;
}
private:
int m_a, m_b;
};
int main()
{
Test t1(20,30);
Test t2 = t1; //默认执行拷贝构造函数。
Test t1(20,30);
// Test t2 = t1; //第一种方法:默认执行拷贝构造函数。
Test t3(t1); // 第二种方法,用t1对象初始化t2对象
system("pause");
return 1;
}
拷贝构造函数第三种用法
结论1:若返回的匿名对象,赋值给另外一个同类型的对象,那么匿名对象会被析构
//Location B;
结论2:B = g(); //用匿名对象 赋值 给B对象,然后匿名对象析构
//若返回的匿名对象,来初始化另外一个同类型的对象,那么匿名对象会直接转成新的对象
class Location
{
public:
Location(int xx = 0, int yy = 0)
{
X = xx; Y = yy; cout << "Constructor Object.\n";
}
Location(const Location & p) //复制构造函数
{
X = p.X; Y = p.Y; cout << "Copy_constructor called." << endl;
}
~Location()
{
cout << X << "," << Y << " Object destroyed." << endl;
}
int GetX() { return X; } int GetY() { return Y; }
private: int X, Y;
};
//alt + f8 排版
void f(Location p)
{
cout << "Funtion:" << p.GetX() << "," << p.GetY() << endl;
}
void mainobjplay()
{
Location A(1, 2); //形参是一个元素,函数调用,会执行实参变量初始化形参变量
f(A);
}
void main()
{
mainobjplay();
system("pause");
}
拷贝构造函数的调用时机*
1)当类中没有定义任何一个构造函数时,c++编译器会提供默认无参构造函数和默认拷贝构造函数
2)当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数
3) 当类中定义了任意的非拷贝构造函数(即:当类中提供了有参构造函数或无参构造函数),c++编译器不会提供默认无参构造函数
4 )默认拷贝构造函数成员变量简单赋值
总结:只要你写了构造函数,那么你必须用。
深拷贝和浅拷贝
浅拷贝出现的原因
class Name
{
public:
Name(const char *myp)
{
int len = strlen(myp);
p = (char *)malloc(len + 1);//有斜杠0.
strcpy(p, myp);
}
~Name()
{
if (p != NULL)
{
free(p);
p = NULL;
len = 0;
}
}
private:
char *p;
int len;
};
void displaymain()
{
Name obj1(“22222”);
Name obj2 = obj1; //执行了默认的拷贝函数,执行的是浅拷贝。
}
void main()
{
displaymain();
cout << “Hello” << endl;
system(“pause”);
}
浅拷贝: 只是把栈中对象的值拷贝了一次,并没有在堆中在把内存空间拷贝一份。Name obj2 = obj1; //执行了默认的拷贝函数,执行的是浅拷贝。
Name obj2 = obj1; //执行了默认的拷贝函数,执行的是浅拷贝。
浅拷贝出现的原因:执行 obj2 = obj1的时候,实在栈中拷贝了一次,然后并没有在堆中开辟新的空间。执行完obi2时候,析构了堆中的地址空间。这时候obj1就出现了‘’野指针‘’情况,当析构obj1时候,就会出现错误。
解决浅拷贝,手动添加一个赋值构造函数变为深拷贝。
**将上面的代码变为深拷贝:**
class Name
{
public:
Name(const char *myp)
{
len = strlen(myp);
p = (char *)malloc(len + 1);//有斜杠0.
strcpy(p, myp);
}
// 手工编写拷贝构造函数,使用深拷贝。Name obj2 = obj1; //执行了默认的拷贝函数,执行的是浅拷贝。
Name(const Name& obj) //手工编写拷贝函数,是深拷贝。
{
len = obj.len;
p = (char *)malloc(len + 1);
strcpy(p, obj.p);
}
~Name()
{
if (p != NULL)
{
free(p);
p = NULL;
len = 0;
}
}
private:
char *p;
int len;
};
void displaymain()
{
Name obj1(“22222”);
Name obj2 = obj1; //执行了默认的拷贝函数,执行的是浅拷贝。
}
void main()
{
displaymain();
cout << “Hello” << endl;
system(“pause”);
}
注:Name obj1(“22222”);
Name obj2 (“”333333“”);
obj2=obj1;//这种情况下也是浅拷贝。只是把指针指在了一起,obj2析构时,obj1又出现了,野指针的情况。
对象的初始化列表
1.必须这样做:
如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,没有默认构造函数。这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,
如果没有初始化列表,那么他将无法完成第一步,就会报错。
实验代码如下:
class A
{
public:
A(int _a)
{
a = _a;
}
private:
int a;
};
class B
{
public:
B(int a, int b)
{
b1 = a;
b2 = b;
}
private:
int b1;
int b2;
A a1;//初始化A的时候,A没有合适的构造函数,所以分配失败。
A a2;
};
int main()
{
A a1(10);
B objb(10,20);
cout << "222" << endl;
system("pause");
return 1;
}
代码分析:代码执行到A a1时就会报错,原因是因为,B中开辟内存空间的时候,不能调用A中的默认构造函数,这样导致了错误。解决方案就是对象的初始化列表。
代码如下:
class A
{
public:
A(int _a)
{
a = _a;
}
private:
int a;
};
class B
{
public:
B(int a, int b):a1(2),a2(5) //
{
b1 = a;
b2 = b;
}
private:
int b1;
int b2;
A a1;//初始化A的时候,A没有合适的构造函数,所以分配失败。
A a2;
};
int main()
{
A a1(10);
B objb(10,20);
cout << “222” << endl;
system(“pause”);
return 1;
}
如上代码加粗的一行。
如果组合对象有多个,执行顺序如下:
class A
{
public:
A(int _a)
{
a = _a;
}
~A()
{
cout << "A的析构函数执行" << endl;
}
private:
int a;
};
class B
{
public:
B(int a, int b,int m,int n):a1(m),a2(n)
{
b1 = a;
b2 = b;
}
~B()
{
cout << "B的析构函数执行" << endl;
}
private:
int b1;
int b2;
A a1;//初始化A的时候,A没有合适的构造函数,所以分配失败。
A a2;
};
int main()
{
// A a1(10);
B objb(10,20,2,3);
cout << "222" << endl;
system("pause");
return 1;
}
代码执行顺序如下:
先执行A对象a1的构造函数,再执行a2对象的构造,在执行b1的构造函数,
析构函数执行顺序:b1,a2,a1.
**注:**如果类中有const,那么必须被初始化。
强化练习
class ABCD
{
public:
ABCD(int a,int b,int c)
{
this->a = a;
this->b = b;
this->c = c;
}
~ABCD()
{
cout << "~ABCD() construct ," << a << b << c << endl;
}
int getA()
{
return this->a;
}
private:
int a;
int b;
int c;
};
class MyE
{
public:
MyE():abcd1(1,2,3),abcd2(1,2,3),m(100)
{
cout << "MyE()" << endl;
}
~MyE()
{
cout << "~MyE()" << endl;
}
MyE(const MyE & obj):abcd1(7,8,9),abcd2(10,11,12),m(100)
{
cout << "MyE(const MyE & obj)" << endl;
}
public:
ABCD abcd1;
ABCD abcd2;
const int m;
};
int doThis(MyE mye1) //会调用Mye类的拷贝构造函数。
{
cout << "doingthis" << mye1.abcd1.getA() << endl;
return 1;
}
int run2()
{
MyE myE;
doThis(myE);
return 1;
}
int main()
{
run2();
system("pause");
return 1;
}
输出结果如下:
123
456
MyE()
789
101112
MyE(const MyE & obj)
doingthis7
~MyE()
~ABCD() construct ,101112
~ABCD() construct ,789
~MyE()
~ABCD() construct ,456
~ABCD() construct ,123
请按任意键继续. . .
对象的动态建立和释放
1new和delete基本语法
1)在软件开发过程中,常常需要动态地分配和撤销内存空间,例如对动态链表中结点的插入与删除。在C语言中是利用库函数malloc和free来分配和撤销内存空间的。C++提供了较简便而功能较强的运算符new和delete来取代malloc和free函数。
注意: new和delete是运算符,不是函数,因此执行效率高。
分配基础数据类型:
int *p = new int; //声明基础数据类型。
*p = 20;
cout << *p << endl;
数组的分配和释放:
int *p1 = new int[10];
p1[2] = 111;
delete[] p1;
创建类:
class Test
{
public:
Test(int a,int b)
{
this->a = a;
this->b = b;
cout << a << b << endl;
}
~Test()
{
cout << "使用了构造函数" << endl;
}
private:
int a, b;
};
Test *pt = new Test(10, 20);
delete pt;
new和delete操作说明,new可以执行构造函数和delete可以执行构造函数。
new和delete深入分析
问题来源:
用new 开辟的内存free可以释放吗?
用malloc开辟的内存delete可以释放吗?
在基础类型中可以执行,但是在类中会出现下面的情况:
Test *pt = new Test(10, 20); //会调用类的构造函数
free(pt);//不调用类的析构函数。
Test *p2 = (Test *)malloc(sizeof(Test)); //不会调用类的构造函数。
delete p2; //会自动调用类的析构函数。