类(class)是对于结构体(struct)的增强,即在struct的基础上增加了一些特性,来满足一些新的需求。
访问修饰符
1.public //可以在外部被访问
2.private //只能在类的内部被访问,不能被外部访问
3.protect //该成员不能被外部访问,同private;可以被子类继承,同public
访问成员变量的方式
class object
{
public:
int a;
double b;
}
void main()
{
//访问方式1
object obj;
obj.a = 10;
//访问方式2
object *p = &obj; //指针类型
p->a = 10;
renturn;
}
内存视图
class类型的内存视图,原理上和struct一样:各成员依次排列在内存中。
类的大小由成员变量相关,和成员函数的数量无关,成员函数不占空间,如果函数被声明为virtual,则大小会发生变化,一般是4个字节,由编译器决定,不同的编译器可能不同。
this指针
1.不受到访问修饰符(public/private)的限制
2.可以访问成员变量以及成员函数
3.this->可以省略,编译器会自动会帮你加上
重名问题
就近原则,如果要访问全局变量/函数而非成员变量/函数,则需要加上::,例如::x,如果要访问成员变量/函数,则需要加上this->
命名规范
1.类名——每个单词的首字母大写,一般用名词形式,例如GoodObject;
2.成员函数——每个单词的首字母大写,一般用动词形式,例如SendMessage;
3.成员变量——m_开头,小写开头,第二个单词开头大写,例如m_maxSize;
头文件与源文件
函数如果写在类体里面,编译器按照inline函数的规则编译;若写在类体之外,则是普通函数。
构造函数
1.函数名必须与类名相同,普通函数不能与类名相同
2.没有返回值
3.构造函数可以带参数,可以重载
4.构造函数不显式调用,只是在创建对象时被编译器自动调用
5.构建函数作用——初始化对象
6.必须要有默认的构造函数,不然该类就不能构造数组
7.如果类没有任何构造函数,则编译器会自己家一个默认的构造函数
拷贝构造函数
1.是构造函数的一种,所以函数名是类名,没有返回值
2.是特殊的构造函数:参数形式是固定的
3.并不显示调用,而是由编译器隐式调用
4.当没有拷贝构造函数时,编译器会默认提供一个拷贝构造函数,默认将每一个成员逐个拷贝
5.拷贝构造函数必须为引用拷贝构造函数必须为引用
class Object
{
public:
Object(const Object& other) //拷贝构造函数必须为引用
{
//拷贝构造函数
}
Object()
{
//普通构造函数
}
//赋值运算函数代码详见《剑指offer》25页面试题1
//赋值函数,即赋值运算符重载
//赋值函数指的是两个对象a和b都已经被构造,所以只需要将a复制给b
//而拷贝构造函数指的是,b还没有被事先构造
Object& b = (const Object &a)//赋值函数(赋值运算符重载)
{
//将a中的成员变量复制到b
return *this;
}
}
void Test(Object obj)
{
//...
}
//拷贝构造函数的三种调用方式
Object a;
Object b(a); //等价于Object b = a;
Object *p = new Object(a);
Test(a);
//attention
Object b(a); //等价于Object b = a, 拷贝,会调用拷贝构造函数
Object b;
b = a; //赋值,不调用拷贝构造函数
析构函数
1.名称固定,类名前加上~
2.没有返回值
3.不能带任何参数,不能重载
4.不显式调用,对象被销毁的时候,编译器自动调用
5.析构函数作用——对象销毁之前,清理善后工作,释放资源,比如释放内存,关闭打开了的文件
6.如果类没有析构函数,则编译器会自己家一个默认的析构函数
成员的初始化与析构
若成员变量也是class时,对象被构造时,成员变量也被构造;对象被析构时,成员变量也被析构。
1.成员变量构造->对象构造->对象析构->成员变量析构
2.如果成员变量有多个是class的对象,则写在前面的那个成员变量先构造,后析构
3.初始化列表,在构造函数后面指定类的成员的初始化构造参数
class Object
{
public:
Object():m_child(1, 2)
{
}
~Object()
{
}
child m_child;
}
class Child
{
public:
Child (int x, int y)
{
}
~Child ()
{
}
int x, y;
}
类的继承
//B(子类,派生类)继承于A(父类,基类)
//子类自动继承父类中所有的public成员
//子类继承父类时,内存中,父类的成员在前面,子类成员在后面,对于父类的私有成员,也会出现在子类的内存空间中,只是编译器限制了子类的访问
class B : public A
{
}
//创建子类对象,则调用父类构造函数->子类构建函数->子类析构函数->父类析构函数
//若父类存在多个构造函数,则默认调用默认构造函数,若需要调用指定构造函数
class B : public A
{
public:
B():A(1,1)
{
//...
}
}
函数重写相关
//函数重写,子类可以重写父类的函数
class A
{
void Test()
{
cout<<"A";
}
}
class B : public A
{
void Test()
{
cout<<"B";
}
}
//如果只是想在父类函数的基础上添加代码,则可以
class A
{
void Test()
{
cout<<"A";
}
}
class B : public A
{
void Test()
{
A::Test();
cout<<"B";
}
}
//用父类指针指向子类对象,从语法上来讲是直接允许的
Tree* p = new AppleTree; //苹果树也是树
//此时,若存在函数重写(不是virtual),则调用的是父类的函数,若函数为virtual,则调用的是子类的函数
//即加上了virtual后,会根据对象的实际类型,决定调用那个函数
//关键字virtual
1.当一个成员函数需要子类重写,那么在父类中应该将其声明为virtual,有时将声明为virtual的函数称为虚函数
2.若父类中的函数为virtual,则子类中的函数默认为virtual,关键字virtual可以加,也可以不加
3.当一个类被继承时,父类的析构函数应该被声明为virtual,否则会存在潜在的问题
Parent* p = new Child();
delete p; //调用的是父类的析构函数,则部分资源没有被释放
4.构造函数不能加virtual,否则编译器会直接报错
//多重继承
//如果AB中存在相同的变量或者成员函数,编译器会报错
class C : public A, public B
{
}