类和对象
将定义某个事物的属性(数据)以及某个事物可使用这些属性执行的操作(函数)整合在一起。这种结构就是类。
声明类
class Circular
{
public:
UINT32 radius;
UINT32 centerPoint;
VOID DrawCircular(VOID);
};
术语“方法”,指的就是类的成员函数。
作为类实例的对象
类相当于蓝图,仅声明类并不会对程序的执行产生影响。在程序执行阶段,对象是类的化身。要使用类的功能,通常需要创建其实例—对象,并通过对象访问成员方法和属性。
创建Circular对象1:
Circular minCircular;
minCircular.radius = 50;
minCircular.centerPoint = 100;
minCircular.DrawCircular();
创建Circular对象2:使用new为Circular对象动态分配内存
Circular *minCircular = new Circular();
minCircular->radius = 50;
minCircular->centerPoint = 100;
minCircular->DrawCircular();
delete minCircular;
使用句点运算符访问成员
类声明表明, maxCircular有 radius等属性,可使用句点运算符( .)来访问:
Circular maxCircular;
maxCircular.radius = 10;
如果有一个指针 firstWoman,它指向 Human 类的一个实例,则可使用指针运算符( ->)来访问成员,也可使用间接运算符( *)来获取对象,再使用句点运算符来访问成员:
Circular *minCircular = new Circular();
minCircular->radius = 50;
(*minCircular).centerPoint = 100;
关键字public和private
C++关键字 public和 private可以指定哪些部分可从外部(如 main( ))访问,哪些部分不能。
以下函数定义了name和age两个成员变量不能够被外部访问,只能够通过成员函数为其赋值。
除了能够将类设计容器, 以封装数据以及操作数据的方法外, C++能够使用关键字 private指定哪些信息不能从外部访问(即在类外不可用)。另外,可将方法声明为公有的( public),以便从外部通过这些方法访问私有信息。
class man
{
private:
char *name;
int age;
public:
void SetManInfo(char *mName, int age);
void OutManInfo(void);
};
void man::SetManInfo(char *mName, int mAge)
{
name = mName;
age = mAge;
}
void man::OutManInfo(void)
{
cout << "name is: " << name << "\nage is: " << age << endl;
}
int main()
{
man *oldMan = new man();
//oldMan->name = 10; //ERROR
oldMan->SetManInfo("sb", 84);
oldMan->OutManInfo();
return 0;
}
构造函数
构造函数是一种特殊的函数(方法),在根据类创建对象时被调用。与函数一样,构造函数也可以重载。
声明和实现构造函数
构造函数是一种特殊的函数,它与类同名且不返回任何值。
class man
{
public:
man();
private:
int age;
};
何时及如何使用构造函数
构造函数总是在创建对象时被调用,这让构造函数成为将类成员变量( int、指针等)初始化为选定值的理想场所。
可在不提供参数的情况下调用的构造函数被称为默认构造函数。可在不提供参数的情况下调用的构造函数被称为默认构造函数。
man::man()
{
age = 1;
}
::被称为作用域解析运算符。
重载构造函数
与函数一样,构造函数也可重载。
class man
{
public:
man()
{
cout << "this is old man" << endl;
}
man(char *name)
{
cout << "this is young man, name is " << name << endl;
}
};
int main()
{
man *oldMan = new man();
man *youngMan = new man("zhangsan");
delete oldMan;
delete youngMan;
return 0;
}
包含初始化列表的构造函数
一种初始化函数成员变量的方法是直接在构造函数内加初始值,而另一种如下:
class man
{
private:
int age;
char *name;
public:
man(int mAge, char *mName)
: age(mAge), name(mName)
{
cout << "this is young man, name is " << name << "age is " << age << endl;
}
};
int main()
{
man *youngMan = new man(100, "zhangsan");
delete youngMan;
return 0;
}
声明和实现析构函数
析构函数可在类声明中实现,也可在类声明外实现。在类声明中实现(定义)析构函数的代码类似于下面这样:
class man
{
public:
~man()
{
}
};
或者
class man
{
public:
~man();
};
man::~man()
{
}
何时及如实使用构造函数
当对象不再在作用域内或通过 delete 被删除进而被销毁时,都将调用析构函数。这使得析构函数成为重置变量以及释放动态分配的内存和其他资源的理想场所。
#include <iostream>
#include <string.h>
using namespace std;
class MyString
{
private:
char *str;
public:
MyString(const char *strBuf)
{
if (NULL != strBuf)
{
str = new char[strlen(strBuf + 1)];
strcpy(str, strBuf);
}
else
{
str = NULL;
}
}
~MyString()
{
if (NULL != str)
{
cout << "delet" << endl;
delete[] str;
}
}
public:
int GetStrLen()
{
return strlen(str);
}
const char *GetStrBuffer()
{
return str;
}
};
int main()
{
MyString *str1 = new MyString("hello word!!!");
cout << "str = " << str1->GetStrBuffer() << endl;
delete str1;
return 0;
}
析构函数不能重载,每个类都只能有一个析构函数。
如果没有实现析构函数,编译器将创建一个伪( dummy)析构函数并调用它。伪析构函数为空,即不释放动态分配的内存。
复制构造函数
浅复制存在的问题
如果两个对象指向同一块动态分配的内存。销毁其中一个对象时, delete[]释放这个内存块,导致另一个对象存储的指针拷贝无效。这种复制被称为浅复制,会威胁程序的稳定性。
#include <iostream>
#include <string.h>
using namespace std;
class MyString
{
private:
char *buffer;
public:
MyString(const char *initString) // Constructor
{
buffer = NULL;
if (initString != NULL)
{
buffer = new char[strlen(initString) + 1];
strcpy(buffer, initString);
}
}
~MyString() // Destructor
{
cout << "Invoking destructor, clearing up" << endl;
delete[] buffer;
}
int GetLength()
{
return strlen(buffer);
}
const char *GetString()
{
return buffer;
}
};
void UseMyString(MyString str)
{
cout << "String buffer in MyString is " << str.GetLength();
cout << " characters long" << endl;
cout << "buffer contains: " << str.GetString() << endl;
return;
}
int main()
{
MyString sayHello("Hello from String Class");
UseMyString(sayHello);
return 0;
}
对象 sayHello 被复制到形参 str,并在 UseMyString( )中使用它。编译器之所以进行复制,是因为函数 UseMyString( )的参数 str被声明为按值(而不是按引用)传递。对于整型、字符和原始指针等 POD 数据,编译器执行二进制复制,因此 sayHello.buffer 包含的指针值被复制到 str 中,即 sayHello.buffer 和 str.buffer 指向同一个内存单元,二进制复制不复制指向的内存单元,这导致两个 MyString 对象指向同一个内存单元。函数UseMyString( )返回时,变量 str 不再在作用域内,因此被销毁。为此,将调MyString 类的析构函数,而该析构函数使用 delete[]释放分配给 buffer 的内存。这将导致 main( )162 第 9 章 类和对象中的对象 sayHello 指向的内存无效,而等 main( )执行完毕时, sayHello 将不再在作用域内,进而被销毁。
使用复制构造函数确保复制深度
复制构造函数是一个重载的构造函数,每当对象被复制时,编译器都将调用复制构造函数。
class MyString{
public:
MyString(const MyString ©Source);
};
MyString::MyString(const MyString ©Source)
{
}
复制构造函数接受一个以引用方式传入的当前类的对象作为参数。这个参数是源对象的别名,使用它来编写自定义的复制代码,确保对所有缓冲区进行深复制。
#include <iostream>
#include <string.h>
using namespace std;
class MyString
{
private:
char *buffer;
public:
MyString(const char *initString) // constructor
{
buffer = NULL;
cout << "Default constructor: creating new MyString" << endl;
if (initString != NULL)
{
buffer = new char[strlen(initString) + 1];
strcpy(buffer, initString);
cout << "buffer points to: 0x" << hex;
cout << (unsigned int *)buffer << endl;
}
}
MyString(const MyString ©Source) // Copy constructor
{
buffer = NULL;
cout << "Copy constructor: copying from MyString" << endl;
if (copySource.buffer != NULL)
{
// allocate own buffer
buffer = new char[strlen(copySource.buffer) + 1];
// deep copy from the source into local buffer
strcpy(buffer, copySource.buffer);
cout << "buffer points to: 0x" << hex;
cout << (unsigned int *)buffer << endl;
}
}
// Destructor
~MyString()
{
cout << "Invoking destructor, clearing up" << endl;
delete[] buffer;
}
int GetLength()
{
return strlen(buffer);
}
const char *GetString()
{
return buffer;
}
};
void UseMyString(MyString str)
{
cout << "String buffer in MyString is " << str.GetLength();
cout << " characters long" << endl;
cout << "buffer contains: " << str.GetString() << endl;
return;
}
int main()
{
MyString sayHello("Hello from String Class");
UseMyString(sayHello);
return 0;
}
main( )将 sayHello 按值传递个函数 UseMyString( ),将自动调用复制构造函数。
通过在复制构造函数声明中使用 const,可确保复制构造函数不会修改指向的源对象。另外,复制构造函数的参数必须按引用传递,否则复制构造函数将不断调用自己,直到耗尽系统的内存为止。