摘要:本文主要向大家介绍了C/C++知识点之C++语言学习(四)——类与对象,通过具体的内容向大家展示,希望对大家学习C/C++知识点有所帮助。
本文主要向大家介绍了C/C++知识点之C++语言学习(四)——类与对象,通过具体的内容向大家展示,希望对大家学习C/C++知识点有所帮助。
C++语言中,构造函数是与类名相同的特殊成员函数。
在类对象创建时,自动调用构造函数,完成类对象的初始化。类对象本身是变量,在栈、堆上创建的对象,对象的成员初始化为随机值;在静态存储区创建的对象,对象的成员初始化为0。
构造函数声明的语法如下:classname(parameters);
没有参数的构造函数称为无参构造函数。当类中没有定义构造函数(包括拷贝构造函数)时,编译器默认提供一个无参构造函数,并且其函数体为空。如果类中已经定义了构造函数(包括拷贝构造函数),编译器不会再提供默认的无参构造函数。
#include
using namespace std;
class Person
{
public:
const char* name;
int age;
void print()
{
printf("My name is %s, I'm is %d years old.\n",name,age);
}
};
int main(int argc, char *argv[])
{
Person p;//编译器提供默认的无参构造函数
p.name = "Bauer";
p.age = 30;
Person p1 = p;//编译器提供默认的拷贝构造函数
p1.print();
return 0;
}
上述代码中,编译器提供了默认的无参构造函数和拷贝构造函数。
#include
using namespace std;
class Person
{
public:
const char* name;
int age;
Person(const Person& another)
{
name = another.name;
age = another.age;
}
void print()
{
printf("My name is %s, I'm is %d years old.\n",name,age);
}
};
int main(int argc, char *argv[])
{
//error: no matching function for call to 'Person::Person()'
Person p;//编译器不在提供默认的无参构造函数
p.name = "Bauer";
p.age = 30;
Person p1 = p;
p1.print();
return 0;
}
上述代码中,类中定义了一个拷贝构造函数,编译器不会再提供默认的无参构造函数,创建Person对象时找不到构造函数调用,编译器报错。
构造函数的规则如下:
A、在对象创建时自动调用,完成初始化相关工作。
B、无返回值,与类名相同,默认无参,可以重载,可使用默认参数。
C、一旦显示实现构造函数,C++编译器不再为类实现默认构造函数。
构造函数的调用根据重载函数的匹配规则进行调用。
classname::classname():member1(v1),member2(v2),...
{
//code
}
构造函数的初始化列表中成员的初始化顺序与类中成员的声明顺序相同,与成员在初始化列表中的位置无关,初始化列表先于构造函数的函数体执行。
#include
using namespace std;
class Value
{
private:
int i;
public:
Value(int value)
{
i = value;
cout << i << endl;
}
};
class Test
{
private:
Value value2;
Value value3;
Value value1;
public:
Test():value1(1),value2(2),value3(3)
{}
};
int main(int argc, char *argv[])
{
Test test;
//2
//3
//1
return 0;
}
类中定义的const变量的初始化必须在初始化列表中进行,本质是只读变量。
编译器无法直接得到类的const成员的初始值,因此const成员无法进行符号表。
#include
using namespace std;
class Test
{
private:
const int i;
public:
Test():i(10)
{}
int getI()const
{
return i;
}
void setI(const int value)
{
int* p = const_cast(&i);
*p = value;
}
};
int main(int argc, char *argv[])
{
Test test;
cout << test.getI() << endl;//10
test.setI(100);
cout << test.getI() << endl;//100
return 0;
}
类对象根据存储区域分为三种,其构造顺序如下:
A、局部对象的构造顺序
当程序执行流到达对象的定义语句时进行构造,但是goto语句可能会跳过类对象的构造。
B、堆对象的构造顺序
当程序执行流到达new语句时创建对象,使用new创建对象将自动触发构造函数的调用。
C、全局类对象的构造顺序
全局类对象的构造顺序是不确定的,不同的编译器使用不同的规则确定构造顺序。全局类对象的构造是不确定的,因此尽量不使用有依赖关系的全局对象。
直接调用构造函数将会产生一个临时对象,临时对象的生命周期只有一条语句的时间,临时对象的作用域只在一条语句中。
现代C++编译器在不影响最终结果的前提下,会尽力减少临时对象的产生。实际工程开发中应尽力减少临时对象。A a = A(10);
上述代码实际没有产生临时对象,也没有调用拷贝构造函数,C++编译器实际优化为A a=10;
#include
using namespace std;
class Test
{
private:
int i;
public:
Test(int i)
{
cout << "Test(int i): " << i <<endl;
}
Test(const Test& another)
{
i = another.i;
cout << "Test(const Test& another)" << i<< endl;
}
Test create(int i)
{
return Test(i);
}
};
Test create(int i)
{
return Test(i);
}
int main(int argc, char *argv[])
{
Test test1 = Test(10);//输出:Test(int i): 10
//等价于Test test1 = 10;
Test test2 = create(100);//输出:Test(int i): 100
//等价于Test test1 = 100;
return 0;
}
单个对象创建时,构造函数的调用顺序如下:
A、调用父类的构造过程
B、调用成员变量的构造函数(调用顺序与声明顺序相同)
C、调用类自身的构造函数
析构函数的调用顺序与构造函数相反。
#include
using namespace std;
class Member
{
const char* ms;
public:
Member(const char* s)
{
printf("Member(const char* s): %s\n", s);
ms = s;
}
~Member()
{
printf("~Member(): %s\n", ms);
}
};
class Test
{
Member mA;
Member mB;
public:
Test() : mB("mB"), mA("mA")
{
printf("Test()\n");
}
~Test()
{
printf("~Test()\n");
}
};
Member gA("gA");
int main(int argc, char *argv[])
{
Test test;
return 0;
}
对象定义时会申请对象空间并调用构造函数。
对象声明告诉编译器存在一个对象。
如果构造函数中抛出异常,构造过程立即停止,当前对象无法生成,析构函数不会被调用,对象占用的空间被立即收回。当构造函数中可能抛出异常时使用二阶构造模式。
#include
using namespace std;
class Test
{
public:
Test()
{
cout << "Test()" << endl;
throw 0;
}
};
int main(int argc, char *argv[])
{
Test* p = reinterpret_cast(0x1);
try
{
p = new Test();
}
catch(...)
{
cout << "Exception..." << endl;
}
cout << "p = " << p << endl;//p = 0x1
return 0;
}
拷贝构造函数根据己存在的对象创建新对象,新对象不由构造函数来构造,而是由拷贝构造函数来完成。
拷贝构造函数的声明如下:classname(const classname& xxx);
拷贝构造函数是参数为const classname&的构造函数。当类中没有定义拷贝构造函数时,编译器会提供一个默认拷贝构造函数,其函数体内会进行成员变量的浅拷贝。
#include
using namespace std;
class Person
{
public:
const char* name;
int age;
Person()
{
}
void print()
{
printf("My name is %s, I'm is %d years old.\n",name,age);
}
};
int main(int argc, char *argv[])
{
Person p;
p.name = "Bauer";
p.age = 30;
Person p1 = p;//编译器提供默认的拷贝构造函数
p1.print();
return 0;
}
上述代码中,编译器会为类提供一个默认的拷贝构造函数。
#include
using namespace std;
class Person
{
public:
const char* name;
int age;
void print()
{
printf("My name is %s, I'm is %d years old.\n",name,age);
}
};
int main(int argc, char *argv[])
{
Person p;//编译器提供默认的无参构造函数
p.name = "Bauer";
p.age = 30;
Person p1 = p;//编译器提供默认的拷贝构造函数
p1.print();
return 0;
}
上述代码中,编译器会为类提供一个默认的无参构造函数和默认的拷贝构造函数。
拷贝构造函数的规则如下:
A、编译器可以提供默认的拷贝构造函数。一旦类中定义了拷贝构造函数,编译器不会为类再提供默认的拷贝构造函数。
B、编译器提供的拷贝构造函数是等位拷贝,即浅拷贝。
C、要实现深拷贝,必须要自定义。
浅拷贝后对象的物理状态相同,深拷贝后对象的逻辑状态相同。
编译器可以为类提供默认的拷贝构造函数,一旦类中定义了拷贝构造函数,编译器将不会再为类提供默认拷贝构造函数和默认无参构造函数,因此开发者必须在类中自定义拷贝构造函数的同时自定义构造函数。编译器提供的默认拷贝构造器是等位拷贝,即浅拷贝,如果类中包含的数据元素全部在栈上,浅拷贝也可以满足需求的;如果类中有堆上的数据,则会发生多次析构行为。
浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不仅对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
深拷贝构造函数的示例如下:
#include
#include
using namespace std;
class IntArray
{
public:
IntArray(int len)
{
m_pointer = new int[len];
for(int i=0; i<len; i++)
{
m_pointer[i] = 0;
}
m_length = len;
}
//深拷贝
IntArray(const IntArray& obj)
{
m_length = obj.m_length;
m_pointer = new int[obj.m_length];
for(int i=0; i<obj.m_length; i++)
{
m_pointer[i] = obj.m_pointer[i];
}
}
int length()
{
return m_length;
}
bool get(int index, int& value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
value = m_pointer[index];
}
return ret;
}
bool set(int index, int value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
m_pointer[index] = value;
}
return ret;
}
~IntArray()
{
delete [] m_pointer;
}
private:
int m_length;
int* m_pointer;
};
int main(int argc, char *argv[])
{
IntArray array1(10);
for(int i = 0; i < 10; i++)
{
array1.set(i,i*i);
}
IntArray array2 = array1;
for(int i = 0; i < 10; i++)
{
int value;
array2.get(i,value);
cout << value << endl;
}
return 0;
}
类中未定义拷贝构造函数时,编译器会提供浅拷贝的默认拷贝构造函数:
#include
#include
using namespace std;
class IntArray
{
public:
IntArray(int len)
{
m_pointer = new int[len];
for(int i=0; i<len; i++)
{
m_pointer[i] = 0;
}
m_length = len;
cout << "Constructor" << endl;
}
int length()
{
return m_length;
}
bool get(int index, int& value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
value = m_pointer[index];
}
return ret;
}
bool set(int index, int value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
m_pointer[index] = value;
}
return ret;
}
~IntArray()
{
cout << "DeConstructor" << endl;
cout << m_pointer << endl;
delete [] m_pointer;
m_pointer = NULL;
}
private:
int m_length;
int* m_pointer;
};
int main(int argc, char *argv[])
{
IntArray array1(10);
for(int i = 0; i < 10; i++)
{
array1.set(i,i*i);
}
IntArray array2(array1);
for(int i = 0; i < 10; i++)
{
int value;
array2.get(i,value);
cout << value << endl;
}
return 0;
}
上述代码中,编译器提供的默认构造函数为浅拷贝,只会进行值拷贝。在对象销毁时调用析构函数,会造成释放同一内存空间2次,导致程序异常(经测试,Linux G++编译器编译后程序异常出错,Windows下QtCreator+MinGW编译器没有异常出错)。
需要深拷贝的场景一般是对象中有成员指向系统中的资源。
拷贝构造函数的调用场合如下:
A、创建对象的副本,如A a=b;A c(a)。
B、函数中以对象作为参数或返回值。
析构函数是C++语言中类的特殊的清理函数,在类对象销毁时,自动调用,完成对象的销毁。析构函数的作用,并不是删除对象,而在对象销毁前完成的一些清理工作,如释放申请的系统资源。
析构函数的定义语法如下:
~classname()
{
}
析构函数的规则如下:
A、对象销毁时,自动调用。完成销毁的善后工作。
B、无返值 ,与类名同。无参,不可以重载,不能设置默认参数
析构函数的调用场景如下:
A、栈对象离开其作用域。
B、堆对象被手动delete。
当类中自定义了构造函数,并且构造函数中使用了系统资源如堆空间申请、文件打开等,需要自定义析构函数,在析构函数体内释放使用的系统资源。
析构函数中抛出异常将导致对象使用的资源无法释放。
this指针是编译器在创建对象时,默认生成的指向当前对象的指针。
this指针作用如下:
A、避免构造器的参数与成员名相同。
B、基于this指针的自身引用还被广泛地应用于那些支持多重串联调用的函数中。比如连续赋值。
C、this指针是const类型的指针。
C++编译器默认为每个类重载了赋值操作符,默认的赋值操作符是浅拷贝的,因此当需要进行深拷贝时必须显示重载赋值操作符。用一个己有对象,给另外一个已有对象赋值会调用赋值操作符重载函数。
赋值操作符重载函数的声明如下:classname& operator=(const classname& another);
赋值操作符重载函数的实现如下:
classname& operator=(const classname& another)
{
if(this != &another)
{
//资源分配操作和赋值拷贝
}
return *this;
}
赋值操作符重载的规则如下:
A、C++编译器提供默认的赋值运算符重载。
B、C++编译器提供的赋值操作符重载函数也是等位拷贝,即浅拷贝。
C、如果需要要深拷贝,必须自定义赋值操作符。
D、自定义面临的问题有三个:自赋值、内存泄漏、重析构。
E、赋值操作符函数需要返回引用,且不能用const修饰,其目的是实现连续赋值。
#include
using namespace std;
class IntArray
{
public:
IntArray(int num)
{
m_length = num;
m_array = new int[num];
for(int i = 0; i < num; i++)
{
m_array[i] = 0;
}
}
~IntArray()
{
if(m_array != NULL)
{
delete [] m_array;
m_array = NULL;
}
}
//拷贝构造函数(深拷贝)
IntArray(const IntArray& another)
{
cout << "copy constructor." << endl;
if(this != &another)
{
m_length = another.length();
m_array = new int[another.length()];
for(int i = 0; i < another.length(); i++)
{
m_array[i] = another.getValue(i);
}
}
}
//赋值操作符重载函数(深拷贝)
IntArray& operator = (const IntArray& another)
{
cout << "assign function." << endl;
if(this != &another)
{
delete m_array;
m_length = another.length();
m_array = new int[another.length()];
for(int i = 0; i < another.length(); i++)
{
m_array[i] = another.getValue(i);
}
}
return *this;
}
int length()const
{
return m_length;
}
int getValue(int index)const
{
return m_array[index];
}
void setValue(int index, int value)
{
m_array[index] = value;
}
private:
int* m_array;
int m_length;
};
int main(int argc, char *argv[])
{
IntArray array1(<span class="hljs-number
本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标编程语言C/C+频道!
您输入的评论内容中包含违禁敏感词
我知道了
请输入正确的手机号码
请输入正确的验证码
您今天的短信下发次数太多了,明天再试试吧!
我们会在第一时间安排职业规划师联系您!
您也可以联系我们的职业规划师咨询:
版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
沪公网安备 31011502005948号