面向过程编程:
关注是问题解决的过程步骤(事情是如何解决的),算法
面向对象编程:
关注的是谁能解决问题(类),需要什么样的数据(成员变量),具备什么样的技能(成员函数)才能解决问题
抽象:找出一个能解决问题的“对象”(观察研究对象),找出对解决问题所必须的数据(属性),功能(成员函数)。
封装:把抽象的结构,归结为一个类(数据类型),然后实例化出类对象,设置对象的属性,调用对象的功能达到解决问题的目的
继承:在解决问题前,先寻找之前的类能不能解决问题,或解决部分问题,如果可以则把旧的类型继承后再次拓展,来缩短解决问题的时间,降低解决问题的难度。
多态:对象的多种形态,外部看到一个对象,然后向对象发出指令,对象会根据自身情况做出独特反应。
一、类和对象
1、通过分析“对象”的属性和行为设计出一个类
2、类就是数据类型
简单类型:只能表示一个属性(变量),C/C++内建数据类型
数组类型:可以表示多个属性(变量),类型必须相同
结构类型:可以表示多个属性(变量),但缺少行为(函数)
类类型 :既能表示属性,也能表示行为,一种复合数据类型
3、对象就是类这种数据类型创建出的实例,相当于结构变量
class Student
{
成员变量;
成员函数;
};
Student stu;
二、类的定义与实例化
1、类的一般形式
class 类名:继承方式 父类
{
public/private/protected://访问控制限制符
成员变量:
//构造函数
类名(形参表)
{
}
//析构函数
~类名(void)
{
}
};
2、类的访问控制限定符
public:公有成员,在任何位置都可以访问
private:私有成员,只能类(自己)的成员函数中访问
protected:受保护成员,只能在类(自己)和子类中访问
注意:类中的成员变量和成员函数默认是私有属性private,结构中成员变量和成员函数默认是public
注意:C++中类和结构的区别只有成员函数和成员变量默认访问权限不同
3、构造函数
1、什么是构造函数:类的同名函数就是构造函数,没有返回值
2、什么时候调用,谁调用,调用几次?
创建类对象时会被自己调用(每创建一个类对象就会调用一次),对象整个生命周期中一定会被调用一次,而且只能被调用一次
3、负责干什么
成员变量的初始化,分配相关资源,设置对象的初始状态
class 类名:继承方式 父类
{
//构造函数
类名(形参表)
{
}
};
4、对象的创建过程 (short 两字节)
1、分配类型所需要空间,无论栈还是堆
2、传递实参调用构造函数,完成如下任务(执行顺序):
//如果有 有参构造,没有无参构造,不能默认创建无参构造
//如果有 有参构造,有无参构造,调用无参构造
1、根据继承表依次调用父类的构造函数
2、根据成员变量的顺序依次调用成员变量的构造函数
3、执行构造函数体中的代码
注意:执行构建函数的代码是整个构造函数的最后一步
要保证构造函数代码所需要的一切资源和先决条件在该代码执行前已经准备充分,并得到正确的初始化
5、对象的创建方法
在栈上创建:1、类名 对象;//不需要括号
User user;//调用无参构造
2、类名 对象(实参);
User user("hehe","123456");//调用有参构造
在堆上创建:1、类名* 对象指针=new 类名;
User* user = new User;//调用无参构造
2、类名* 对象指针=new 类名(实参)
User* user = new User("hehe","123456");//调用有参构造
User user = User("hehe","123456");
创建多个对象:
1、类名 对象={(实参),(实参),(实参)};
User user[] = {User("he1","123456"),User("he2","123456")};//调用两次有参函数
2、类名* 对象指针=new 类名[n]{(实参),(实参)};
User* user = new User[10]{User("hehe","1234"),User("hehe","1234")};//调用两次有参函数,其它是无参
注意:通过malloc创建的类对象不能调用构造函数
User* user = (User*)malloc(sizeof(User));
user->User();
注意:通过new[ ]创建的对象,一定要通过delete[ ]释放
6、类的声明、实现、调用
1、在头文件中声明
class 类名:继承方式 父类
{
成员变量:
public/private/protected://访问控制限制符
//构造函数
类名(形参表);
//析构函数
~类名(void);
//其它成员函数
返回值 函数名(参数列表);
};
2、源文件实现类的相关函数
返回值 类名::函数名(参数列表)
{
}
3、调用时只需要导入头文件,然后与类函数所在的源文件一起编译即可
注意:如果一个类内容不多,可以考虑在头文件完全实现
也可以只在头文件实现一些简单的成员函数
注意:类中自动生成的函数,在源文件中实现时,也需要在头文件中声明
List list;
list.add();
三、构造函数与初始化列表
1、构造函数可以被重载(同一个名字的函数有多个不同的版本) 无参 有参(不同的参数类型)
2、缺省(无参)构造 ,编译器自动生成的一个什么都不做的构造函数(唯一的作用就是避免编译错误)
注意:当类实现一个缺省构造后,缺省构造就不会再自动生成,如果有需要必须显式地写出来
3、无参构造未必无参,当给有参构造的所有参数设置默认无参,调用这种构造函数就不需要传参
Student stu();//函数声明
注意:所谓的“编译器生成的某某函数”其实不是真正语法意义上的函数,而是功能意义的函数,编译器作为可执行指令的生成者,它会直接生成具有某项功能的二进制指令,不需要借助高级语言语义上的函数完成此任务
注意:如果一个类A是其它类B的成员变量,那么一定要保证它有一个无参构造,当B的构造函数执行时会先执行成员变量的无参构造,而此时类B是无法给类A成员变量提供参数的
4、单参构造与类型转换
如果构造函数的参数只有一个,那么Test t=n语句就不会出错,它会自动调用单参构造来达到类型转换的效果
const int num;
Test(int n):num(n)
{
}
Test t(10);
cout<<t.num<<endl;
如果想禁止这种类型转换需要在单参构造前加 explicit
5、初始化列表 名字可以一样
类里面不能初始化,它专为类成员进行初始化用的。
构造函数(参数):成员1(参数1),成员2(参数2)....
Test 有一个成员 int num;
Test t={ 10}; //结构体的语法
cout<<t.num<<endl;
通过初始化列表可以给类成员变量传递参数,以此调用类成员的有参构造
class A
{
public:
int num;
A(int _num)
{
num = _num;
cout << "我A的有参构造" << endl;
}
};
class Test
{
public:
string str;
const int num;
int& x;
A a;
Test(int num,const char* str,int& q):num(num),str(str),a(num),x(q)
{
cout << "1234" << endl;
}
};
初始化列表也可以给 const 成员 、引用成员进行初始化
成员的初始化顺序与初始化列表没有关系,而是在类中的定义顺序有关
A a;
//B b;
C c;
B b;
注意:初始化列表运行类成员变量还没有定义成功
const int num = 0;
int* p = (int*)#
*p = 1000;
printf("%d\n",num);
.c文件可以改变,.cpp不能改变 准确的说是:值改变了,但是编译器没有去读
const定义的值不能修改,可以通过加 volatile 修改 volatile const int num = 0;