1. 类和对象
1.1 声明类
类包含:属性(数据),方法(类成员的函数)
使用关键字class声明,如声明一个人类
//使用关键字class声明一个人类
class Human
{
//数据属性 data attributes
string Name;
string DateOfBirth;
string PlacwOfBirth;
//方法 method
void Talk(string TextToTalk);
void IntroduceSelf();
}
1.2 实例化对象
实例化对象的语法:
//实例化对象
Human Tom; //在栈上实例化一个对象
Human *pAHuman = new Human(); //在堆上实例化一个对象
delete pAHuman; //删除对象
1.3 使用句点运算符访问类成员
//使用句点运算符访问类成员
// 访问类成员数据和类成员函数
Human Tom;
Tom.DateOfBirth="1990";
Tom.IntroduceSelf;
//指针pTom形式
Human* pTom = new Human();
(*pTom).IntroduceSelf();
1.4 使用指针运算符(->)访问成员
//使用指针运算符(->)访问成员
//Human Tom;
//Human *Ptom = &Tom;
Human* pTom = new Human();
pTom->DataOfBirth = "1970";
pTom->IntroduceSelf();
2. 关键字public和private
public和private两个关键字能够控制类书香的访问和操纵方式。一般而言,需要用private修饰类成员数据,以保护数据不被暴力修改。面向对象编程中修改对象的数据需要优雅地使用类成员方法对数据进行修改。
//一个包含private和public修饰的类
class Human
{
private:
//私有成员数据
int Age;
string name;
public:
int GetAge()
{
return Age;
}
int SetAge(int InputAge)
{
Age = InputAge;
}
//...其他成员...
};
2.1 private关键字实现数据抽象
private关键字的特点:private指定了不能从类外访问的信息(成员数据和成员函数),对外界隐藏类使用者无需知道的东西
#include<iostream>
using namespace std;
class Human
{
private:
//private member data
int Age;
public:
void SetAge(int InputAge)
{
Age = InputAge;
}
int GetAge()
{
if(Age > 30)
return (Age - 2);
else
return Age;
}
};
int main()
{
Human FirstMan;
FirstMan.SetAge(45);
Human FirstWoman;
FirstWoman.SetAge(23);
cout << "Age of FirstMan "<< FirstMan.GetAge() <<endl;
cout << "Age of FirstWoman " << FirstWoman.GetAge() <<endl;
return 0;
}
3. 构造函数
构造函数是一种特殊的方法,在创建对象时使用。和普通的函数一样,可以被重载。
3.1 声明和实现构造函数
构造函数的名称和类名相同
//声明构造函数
class Human
{
public:
Human()
{
// 构造函数的代码
}
};
//在类外定义构造函数
class Human
{
public:
Human();
};
//类外提供构造函数的实现
Human::Human()
{
//构造函数的代码
}
3.2 何时使用构造函数
构造函数在创建对象时被调用,构造函数可将成员变量(int, string, 指针)初始化为已知值。
3.3 重载构造函数
构造函数可以被重载
可以选择不实现默认构造函数,从而要求实例化对象时必须提供某些参数,以使用重载的构造函数。
//重载构造函数
class Human
{
public:
Human()
{
//默认构造函数
}
Human(string HumansName)
{
//重载构造函数
}
};
3.4 没有默认构造函数的类
如果没有默认构造函数,而提供重载的构造函数时,编译器不会生产默认构造函数。
这样可以使创建每个对象时,都必须提供某些对象的参数。
3.5 带默认值的构造函数参数
//带默认值的构造函数参数
class Human
{
private:
string Name;
int Age;
public:
// 重载构造函数
Human(string HumansName, int HumansAge = 25)
{
Name = HumansName;
Age = HumansAge;
cout << "overload constructor creates " << Name;
cout << " of age " << Age <<endl;
}
// other members
};
3.6 包含初始化列表的构造函数
//包含初始化列表的构造函数
class Human
{
private:
string Name;
int Age;
public:
Human(string InputName, int InputAge):Name(InputName),Age(InputAge)
{
cout << "Constructor with parameters: ";
cout << "Human " << Name <<", "<< Age << " years old"<<endl;
}
};
4. 析构函数
析构函数和构造函数一样,也是一种特殊的函数。析构函数在对象销毁时自动被调用。
析构函数不能被重载。
如果忘记实现析构函数,则编译器会自动生成一个伪(dummy)析构函数并调用它,伪析构函数为空,不会释放动态分配的内存。
4.1 声明和实现析构函数
析构函数的声明
//析构函数的声明
class Human
{
public:
~Human()
{
//销毁成员数据的代码
}
};
4.2 何时使用析构函数
当对象不在作用域或通过delete删除时,将调用析构函数对其进行销毁。析构函数成为了重置变量和释放内存的一种有效手段。
//用类封装一个c风格的字符串
#include<iostream>
using namespace std;
class MyString
{
private:
char* Buffer;
public:
// constructor
MyString(const char* InitialInput)
{
if(InitialInput != NULL)
{
Buffer = new char [strlen(InitialInput)+1];
strcpy(Buffer,InitialInput);
}
else
Buffer = NULL;
}
// distructor
~MyString()
{
cout << "Invoking destructor, cleaning up! "<<endl;
if(Buffer != NULL)
{
delete[] Buffer;
}
}
int GetLength()
{
return strlen(Buffer);
}
const char* GetString()
{
return Buffer;
}
};
int main()
{
MyString SayHello("hello from string class");
cout << "Length: " << SayHello.GetLength() << endl;
cout << "content: " << SayHello.GetString() << endl;
return 0;
}
5. 复制构造函数
调用函数时,如果函数的形参是对象,则传递的实参对象也被复制,因此需要复制构造函数。
5.1 浅复制存在的问题
浅复制是指在传递对象时,参数被复制过程中仅复制了指向内存的指针变量,而没有复制包含内容的内存块。
被浅复制的对象在某个函数中脱离作用域时就被析构,然而主调函数中往往还要对其进行一次析构,这就出现了问题。
5.2 使用复制构造函数确保深复制
声明构造函数的语法:
//拷贝构造函数的实现
class MyString
{
Mystring const(const MyString& CopySource);
};
MyString::MyStrin(const MyString& CopySource)
{
//拷贝构造函数实现代码
};
注意!
- 类包含原始指针成员时,必须要变细人复制构造函数和复制赋值运算符;
- 编写复制构造函数时,必须要将接受原对象的参数声明为const引用;
- 必须将类成员声明为std:string和智能指针类(而非原始指针),因为他们实现了复制构造函数,可以减少工作量;
- 除非万不得已,不要将类成员声明为原始指针;
5.3 C++11中的移动构造函数
如果遇到构造函数被连续调用两次的情况,实际上是不需要将一个对象反复构造两次的,此时应该使用移动复制构造函数
使用移动复制构造函数时,C++11编译器会自动使用它“移动”临时资源,避免深复制。
//拷贝构造函数被连续调用两次的情况
class MyString
{
//...
};
MyString Copy(MyString& Source) //复制string对象的函数
{
MyString CopyForReturn(Source.GetString);
return CopyForReturn;
}
int main()
{
MyString sayHello("hello!");
MyString sayHelloAgain(Copy(sayHello)); //执行两次拷贝构造函数,一次copy返回,一次是作为参数传入
return 0;
}
//移动复制构造函数的声明
MyString(MyString&& MoveSource)
{
if(MoveSource.Buffer != NULL)
{
Buffer = MoveSource.Buffer;
MoveSource.Buffer = NULL;
}
}
6. 构造函数和析构函数的其他用途
6.1 不允许复制的类(单例模式)
用private修饰所有的构造函数(默认构造函数,复制构造函数...)
用static修饰一个函数,这个函数中实例化了一个对象。从而使得该类中仅存在这一个对象。
注意:
- 关键字static用于累的数据成员时,该数据成员将在所有的实例中共享
- 将static用于函数中声明的局部变量时,该变量的值将在两次调用之间保持不变
- 将static用于成员函数时,该方法将在所有实例之间共享
6.3禁止在栈中实例化的类
如果要编写一个数据库类,且内部结构超过1GB,那么就应该禁止在栈上实例化该类,栈的空间没有那么大。
关键在于两点:
- 在栈上实例化对象时,将类的析构函数用private进行修饰,从而使得每次对象脱离作用域时,无法调用析构函数,编译器会检查该错误;而当在堆上实例化对象时,这种方法编译器不会报错,而导致内存泄露。
- 需要提供一个用来销毁实例的静态公有函数,只有这个类成员函数才能够调用析构函数。
7. this指针
- 在类中,this指针包含当前对象的地址
- 静态成员方法不会传递this指针,因为静态方法与实例不关联,由所有实例共享
8. sizeof()用于类
sizeof()是一个运算符,用于确定指定类型需要多少内存。
类实例化为对象后所占的内存实际上是在设计类时就确定了,与结构体类似,也有内存对齐的要求。
类对象所占的内存由类的数据成员决定。如int, bool, char* 等。
9. 结构体和类的区别
除非指定,类的成员默认为私有的,结构体的成员默认为公有的。
10. 声明友元
声明友元函数和友元类时,则友元类和友元函数可以从类外访问类的私有数据成员和方法。
友元函数的声明
//友元函数的声明
private:
//...
friend void function();
//...
//友元类的声明
//...
private:
friend class Human;
//...