1.C++语言的的作用域:
局部作用域;
全局作用域;
类作用域;
2.在《C++Primer》中是这样解释类的:
类类型通常被称为抽象数据类型(abstract data type)。抽象数据类型将数据(即状态)和作用于状态的操作视为一个单元,我们可以抽象地考虑类该做什么,而无需知道类如何完成这些操作。
抽象数据类型是面向对象编程和泛型编程的基础。
3.面向对象语言的四大特征:抽象 封装/隐藏 继承 多态
面向对象语言的三大特征:封装/隐藏 继承 多态
4.定义一个类:
(1)简言之,类就是定义了一个新的类型和新的作用域
(2)创建类的目的,是用来抽象实体的类型,从实体的属性和行为两方面,得到类的成员变量和成员方法,因为类是实体类型的抽象,所以类类型不占空间。
(3)每个类可以没有成员,也可以定义多个成员,成员可以是数据,函数或类型别名。
(4)一个类可以包含若干个public(公有的),private(私有的),protected(受保护的)部分/
(5)用struct和class都可以定义类,但是,在没有访问限定符的情况下,class定义的类默认都是private访问限定,struct都是public访问限定。
(6)对于类成员方法的定义,可以在类内定义,也可以在类外定义,但是要注意的是,如果在类外定义,需要在函数名之前加上类作用域。如果成员方法在类体内定义,就自动处理为inline函数了。
普通成员变量和成员方法需要依赖对象才可以调用。
5.构造函数和析构函数:
(1)构造函数:构造函数是一个特殊的、与类名相同的成员函数,主要用于对对象内存进行初始化
(2)析构函数:析构函数是类名前加~的成员函数,主要作用是对象内存释放前,把占用的其它系统资源释放掉。
值得注意:对象的内存是由系统开辟的,构造函数只是做了内存的初始化,构造函数调用完了,我们就可以理解为对象产生了。所以构造函数不是给对象开辟内存的。同理,析构函数是在对象内存释放之前,将对象占用的外部资源先释放掉(例如释放堆上占用的内存)。所以析构函数也不是释放对象内存的。
(3)构造函数和析构函数没有返回值。
(4)构造函数可以带参数,所以构造函数是可以重载的。析构函数不能带有参数,所以析构函数只能有一个。
(5)当类中没有任何构造/析构函数的时候,编译器会给当前类一个默认的构造/析构函数。(是一个空函数,什么都不做)
(6)如果类中定义了任意构造函数,就不会产生默认构造函数了。
(7)在定义默认构造的对象,不要在构造时在对象名字后面加()。
(8)先构造的函数是后析构的,后构造的函数是先析构的。
6.一个简单的类
#include<iostream>
#include<typeinfo>
using namespace std;
class CPerson//类名
{
public://共有的
/*构造函数 名字与类名相同 可以任意带参*/
CPerson()
{
age = 20;
strcpy(name,"LiMing");
strcpy(address,"BeiJing");
}
CPerson(int ag,char *na,char *add)
{
age = ag;
strcpy(name,na);
strcpy(address,add);
}
void show();
~CPerson(){}
private://私有的
int age;
char name[20];
char address[20];
};
void CPerson::show()// 在类外定义函数需要注明作用域
{
cout<<"年龄:"<<age<<" 名字:"<<name<<" 地址:"<<address<<endl;
}
int main()
{
CPerson people1(20,"jack","Xi'An");
people1.show();//调用成员方法需要依赖对象
CPerson LM;//定义默认构造的对象,不要在对象后加()
LM.show();
}
7.成员函数被const修饰:
将关键字const加在形参之后,就可以将成员函数声明为常量。const成员不能改变其操作的对象的数据成员。const必须同时出现在声明和定义中,若只出现一处,就会出现编译时的错误。
若提供返回名字和地址的操作,函数应该为const的,这样提高了方法的安全性,防止被修改。
8.this指针
一个类可以产生多个成员对象,每个对象都有自己的一份成员变量,它们共享这个成员的成员方法。那么问题来了,即然类的方法是由多个对象共享的,那么成员方法在被调用的时候是怎么知道是哪个对象调用的呢?
这个时候就引入this指针的概念了,成员函数具有一个附加的隐含形参,即指向该类对象的一个指针。这个隐含形参命名为this。
类的成员方法一经编译,形参都会加上当前类类型的this指针变量。
在对象调用成员方法====》把对象的地址作为方法的实参进行传递。
在方法里面访问其他成员,都是通过this->来调用的。
9.浅拷贝和深拷贝
浅拷贝:简单来说,浅拷贝可以理解为直接将值复制给将要拷贝的对象。若其中有变量指向外部资源,那么也只是会直接指向同一块外部资源。
深拷贝:可以理解为,若一个类拥有外部资源,且当这个类的对象发生拷贝的时候,会为新对象分配新的资源。
>预防浅拷贝问题的方法:
1)自定义拷贝构造函数和赋值运算符重载函数
2)将这两个函数私有化,从根本上杜绝浅拷贝的问题
class A
{
public:
A(int a,int b,int c)
:_x(a),_y(b),_z(c)//初始化列表
{}
private:
int _x;
int & _y;
const int _z;
};
10.拷贝构造函数
场景:同类型一个已经存在的对象构造新对象的时候,就会调用拷贝构造函数。
结论:当类中没有提供拷贝构造函数时,编译器就会产生一个默认的拷贝构造函数,做内存拷贝(浅拷贝)。
带来的问题:若类中有成员指向外部资源的时候,若没有自定义拷贝构造函数,那么会做内存拷贝。当被拷贝构造的对象析构时,直接就释放了指向的外部资源,原对象再次析构就会出现问题,因为他所指向的外部资源已经被释放掉了,释放两次势必是会出错的。所以,当对象浅拷贝出错,一定要提供自定义的拷贝构造函数。
class A
{
public:
A()
{
cout<<"A()"<<endl;
a = 10;
mp = new int[a];
}
A(const A &src)//没有任何操作 实行默认拷贝构造(浅拷贝)
{
cout<<"copy A"<<endl;
}
~A()
{
cout<<"~A()"<<endl;
delete []mp;
}
private:
int a;
int *mp;
};
int main()
{
A a;//定义一个A类的对象
A b = a;//已经存在的对象b构造新对象a
return 0;
}
11.赋值运算符重载函数operator=
场景:两个已经存在的对象赋值的时候,会调用该函数。
结论:当类里面没有提供赋值运算符重载函数的时候,编译器就会产生默认的赋值运算符重载函数,作内存拷贝(浅拷贝)。所以同理,如果类中有成员变量指向外部成员的时候,浅拷贝会出错,就应该自定义赋值运算符重载函数。
class A
{
public:
A()
{
cout<<"A()"<<endl;
a = 10;
mp = new int[a];
}
void operator=(const A &src)
{
cout<<"operator "<<endl;
}
~A()
{
cout<<"~A()"<<endl;
delete []mp;
}
private:
int a;
int *mp;
};
int main()
{
A a;
A b;
b = a;
return 0;
}
12.拷贝构造函数的形参可不可以按值传递呢?
回答:不可以。如果类A的拷贝构在函数是以值方式传递的一个类A对象作为参数的话,当需要调用拷贝构造函数的时候,需要以值方式传递一个A类的对象作为实参,而以值方式传递需要调用类A的拷贝构造函数;结果就是调用类A的拷贝构造函数导致又调用了类A的拷贝构造函数,造成无限递归。
13.对象的构造函数初始化列表
如果想在对象成员变量初始化的时候,指定成员变量的初始化方式,那么应该在构造函数的初始化列表中指定。
class A
{
public:
A(int a,int b,int c)
:_x(a),_y(b),_z(c)//初始化列表
{}
private:
int _x;
int & _y;//引用必须在定义的时候初始化,并且不能重新赋值,所以必须在初始化列表中初始化
const int _z;//常量成员只能初始化不能赋值,所以必须在初始化列表中初始化
};
需要注意:成员的初始化顺序是在类中定义出现的顺序初始化的,而不是按照初始化列表的顺序初始化的。