类和对象的关系
类和对象的关系就像设计图和房子的关系——类十构建类的对象的“设计图”。正如一张设计图可以造出很多房子,却不可能住在设计图里面;我们不能直接使用类,但是可以由一个类实例化(创建)很多对象。
对象
观察真实世界,随处都能看到对象(object)——人、动物、汽车等。这些对象都有共同点:
1. 它们都有属性:大小、形状、颜色等
2. 它们都有行为:如球的滚动、弹跳,人的行走、睡觉,车的加速、转弯等
C++中,对象由类实例化而成,所以可以认为对象包含了数据和函数,虽然实际上并非如此:
假如对一个类的类名或该类的一个对象进行sizeof运算,结果将只包含该类的数据成员的大小。
类
在C++等面向对象的语言中中,编程的重点在于类,即重点在于创建自己的用户自定义类型上
这些用户自定义类型称为类
一个类包含:
- 数据成员(data member):例如一个银行账户类可能包含账号和余额
- 成员函数(member function):这在其他面向对象的语言中也被称为方法(method),例如一个银行账户类可能包括存款、取款、查询等成员函数
定义类
class GradeBook{
public:
void displayMessage(){
cout << "Welcome to the Grade Book!" << endl;
}
private:
string courseName;
};
- 以关键字class开始类的定义
- 按照惯例,用户定义的类的名字以大写字母开头
- 在类定义的末尾要记得有一个分号(;)
- 将一个函数定义在另一个函数内部是一个语法错误
- 类内函数的调用使用“.”,如
GradeBook gb; gb.displayMessage();
public和private
成员访问说明符(public,private等)之后必须有冒号(:)
public
public表明该函数或数据十“公共课用的”,也就是说可以被程序中的其他函数或其他类的成员函数所调用(使用)
private
在成员访问说明符private之后声明的变量或者函数,只可以被声明他们的类的成员函数所访问,不能被类之外的函数或其他类的成员函数访问
- 识图通过myGradeBook.courseName之类的表达式访问数据成员courseName将导致编译错误
- 被某个类生命为友元的函数和类,可以访问该类的private成员
- 采用获取函数和设置函数来访问和操纵数据成员是一种良好的变成习惯
对象的初始化——构造函数
在我们创建一个对象的同时初始化数据成员,可以使用构造函数
实际上即便我们没有显式地定义一个构造函数,编译器也会隐式地创建一个默认的构造函数,这个函数不初始化类的数据成员,因此这些成员变量通常包含一个“垃圾”值,如-237538
因此我们最好自定义一个具有实参的构造函数
请注意以下几点:
- 构造函数与它的类同名
- 构造函数在它的形参列表中指定它运行所需的数据
- 构造函数可以有默认实参,如
class Time1{ Time1( int = 0, int = 0, int = 0 ) };
- 构造函数不可以返回任何值(甚至是void)
- 通常情况下,构造函数声明为public
构造函数的互补——析构函数
当对象撤销时,类的析构函数会被隐式调用,如离开对象所在的作用域时
实际上,析构函数本身并不释放对象占用的内存空间,它知识在系统收回对象的内存空间之前执行骚味工作,这样内存可以重新用于保存新的对象。
- 析构函数不接收任何参数,也不返回任何值
- 析构函数不可以指定返回类型,包括void
- 一个类只能有一个析构函数,而析构函数不允许重载
- 函数非正常终止的时候,不调用析构函数
class A{
public:
A( int, string ); //constructor
~A(); //destructor
private:
int id;
string name;
};
类的定义的分离
/* 类的接口
GradeBook.h */
#include <string>
using namespace std;
class GradeBook{
public:
GradeBook( string );
void setCourseName( string );
string getCourseName();
private:
string courseName;
};
/* 类的定义
GradeBook.cpp*/
#include <iostream>
using namespace std;
#include "GradeBook.h"
//constructor
GradeBook::GradeBook(string name){
setCourseName(name);
}
void GradeBook::serCourseName(string name){
courseName = name;
}
string GradeBook::getCourseName(){
return courseName;
}
编译和连接
不细说
类的深入剖析
const对象和const成员函数
程序员可以用关键字const来指定对象是不可修改的,这样任何识图修改该对象的操作都将导致编译错误,如
const Time noon(12,0,0);
将对象声明为const有助于贯彻最小特权原则,甚至可以提高性能
使用const的方法为:
//const对象
const int hour;
//const函数
int getTime() const{
return hour;
}
我们在使用const来定义对象或成员函数的时候,应该注意以下几点:
1. 编译器不允许声明为const的成员函数修改对象,因此将修改对象的数据成员的成员函数定义为const将导致编译错误
2. const成员函数不得调用同一类同一实例的非const成员函数
3. const对象不得调用非const成员函数
4. 构造函数和析构函数都会修改对象,因此不允许对构造函数和析构函数进行const声明
const数据成员
所有的数据成员都可以用成员初始化器进行初始化,而const对象不能通过赋值来初始化,所以const数据成员和引用的数据成员必须使用成员初始化器进行初始化
成员初始化器的位置在构造函数的 参数列表和左花括号 之间,跟const类似,并用一个冒号(:)与参数列表隔开,初始化的形式为:数据成员名称和圆括号包含的初始化值,如下:
class Add{
public:
Add(int c=0,int i=0)
:count(c),
increment(i)
{
//empty
}
private:
int count;
const int increment;
};
- 将所有运行时不修改对象的成员函数都声明为const是一个良好的编程习惯
组成(composition)
组成即 一个类将其他类的对象作为成员
类类型的数据成员用成员初始化器的方法初始化
- 类的对象的构造是由内而外进行创建的,即先创建类内的类对象,再创建类
- 类的对象的撤销(析构)则是完全相反的顺序,即由外而内进行析构
friend函数和friend类
friend函数在类的作用域之外定义,但是拥有访问类的所有成员的权限,但它并不是成员函数
//friend函数的定义
class Count{
friend void setX( Count &, int ); //friend function
public:
Count():x(0){}
private:
int x;
};
void setX(Count &c,int val){ //友元函数并不是成员函数
c.x=val;
}
friend类
//friend类的声明
class A{
public:
friend class B; //friend class
};
友元的关系既不是对称的也不是传递的,比如:
A是B的友元,B是C的友元,那么不能说A是C的友元(不传递),或者B是A的友元(不对称)
this指针
每一个对象都可以使用一个称为this的指针来访问自己的位置,而this指针并不是对象本身的一部分,相反,this被当作一个隐式的参数传递给对象的每一个非static成员函数
//this指针的使用
class Test{
public:
Test(int=0);
void print() const;
private:
int x;
};
void Test::print() const{
cout << x <<endl; //隐式调用
cout << this->x <<endl; //箭头调用
cout << (*this).x <<endl; //圆点调用
}
//使用this达到的串联的函数调用
class Time{
public:
Time(int=0,int=0,int=0);
Time &setHour(int h){ hour=h; return *this; }
Time &setMinute(int m){ minute=m; return *this; }
Time &setSecond(int c){ second=s; return *this; }
private:
int hour;
int minute;
int second;
};
int main(){
Time t;
//串联的函数调用
t.setHour(18).setMinute(30).setSecond(22);
圆点运算符的结合是从左到右的,因此三个函数的运行顺序是从左到右的
}
new和delete:内存的动态管理
C++允许程序员在程序中对任何内置的或用户自定义的类型控制的内存进行分配和释放,这称为动态内存管理,有运算符new和delete完成
//为一个对象分配内存空间,返回一个指向对象类型的指针
Time *timePtr;
timePtr = new Time;
//撤销一个动态分配的对象并释放空间
delete timePtr;
//动态分配的同时提供初始化值
double *ptr = new double(3.14); //基本类型
Time *timePtr = new Time(1,2,3); //自定义对象
//动态地分配数组
int *array = new int[10];
int n = 7;
int *Array = new int[n];
//释放动态分配的数组的内存
delete [] array;
delete [] Array;
static 类成员
类的每个实例对象都格子拥有类的所有数据成员的一份副本,但是static成员例外,它的一份副本是被类的所有对象共享的
- static与全局变量不同,它们只在类的作用域起作用
- static可以被生命为public、private或protected
- static只能被初始化一次,默认情况下将初始化为0(基本类型)
- 除int或enum类型的const static 数据成员之外,其他所有static成员必须在类定义体之外继续宁定义
- 即使不存在已实例化的对象,类的static成员或函数仍存在并可以使用
- static成员函数里不可使用this指针
- static成员函数不可声明为const
class Student{
public:
Student(string n){ allStu++; name=n; }
static int getStudent(){ return allStu; }
private:
string name;
static int allStu; //int类型的static成员数据可以在class内定义
};
int Student::allStu = 0;
int main(){
//没有类的实例的时候,该如何调用static函数
cout<< Student::getStudent()<< endl;
Student *jack=new Student("jack");
Student *tom=new Student("tom");
//两者结果一样
cout<< jack->getStudent()<< endl;
cout<< tom->getStudent()<< endl;
}
Reference:
- c++大学教程-5th