1、面向过程和面向对象
面向过程(Procedure Oriented)是一种以过程为中心的编程思想,是一种自顶而下的编程模式。最典型的面向过程的编程语言就是C语言。
举例说明:点外卖通过面向过程可以看作下单,做菜,送餐,这三个过程;分别通过函数实现,这就是典型的面向过程。面向过程代码都是走流程一样,明确每一步的工作,这样代码执行效率虽然很高,但是代码扩展性差,维护难度也较大。
面向对象 是把事物给对象化,包括其属性和行为,通过对象之间的交互实现各种功能。面向对象编程更贴近实际生活的思想。
举例说明:同样是点外卖,面向对象注重的就是顾客、骑手、商家三者之间的关系,将这三者封装成三个类,每个类包含各自的属性以及行为,比如骑手就是接受订单、送餐等行为,保证各司其职,一定程度解耦,减少后期代码的维护难度,可扩展性也大大提高。
2.类的引入
在C语言中,struct被当作结构体关键字来使用,里面只能定义变量;而在C++中,struct内不仅可以定义变量还可以定义函数。
#include<iostream>
#include<string.h>
using namespace std;
/*
//C语言的学生类
struct Student
{
char _name[20];
int _ID;
int _age;
};*/
//C++的学生类
struct Student
{
//初始化
void Init(const char* name, int ID, int age)
{
strcpy(_name, name);
_ID = ID;
_age = age;
}
//打印
void Print()
{
cout << _name << " " << _ID << " " << _age << endl;
}
char _name[20];//姓名
int _ID; //学号
int _age; //年龄
};
int main()
{
Student s; //实例化一个学生对象
s.Init("张三", 123, 14);//调用初始化函数
s.Print(); //调用打印函数
return 0;
}
C++更倾向于使用class来定义类
3.类的定义
class className
{
// 类里面添加成员函数和成员变量}; // 注意这里是有分号的
class是定义类的关键字,className就是类名,花括号内就是类的主体,千万别忘了最后有一个分号
以上面的学生类为例:学号、姓名、年龄就是类的属性(成员变量),而 Init() 和 Print() 就是类的行为(成员函数),main 函数里的变量 s 就是通过 Student 学生类实例化出来的对象。
类的定义方式
1、声明和定义都在全部都在类体中,特别声明:如果成员函数在类中定义,小函数可能会被编译器优化为内联函数。
class Student
{
public:
void Init(const char* name, int ID, int age)
{
strcpy(_name, name);
_ID = ID;
_age = age;
}
char _name[20];
int _ID;
int _age;
};
2、声明和定义分开编译,声明放在.h文件中,定义放在.cpp文件中
//.h中声明
class Student
{
public:
void Init(const char* name, int ID, int age);
char _name[20];
int _ID;
int _age;
};
//.cpp中定义
void Student::Init(const char* name, int ID, int age)
{
strcpy(_name, name);
_ID = ID;
_age = age;
}
项目中一般都会采用第二种方式,日常练习就可以用第一种方式(比较方便)。
4.类的访问限定符及封装
访问限定符
C++通过封装用类将对象的属性和行为结合在一起,让对象功能更加完善,然后通过访问限定符选择性的对外提供用户使用的接口。
访问限定符有三种:public(公有)、protected(保护)、private(私有)
说明:
1、公有public修饰的成员在类外可以直接通过类实例化的对象访问。
2、protect和private修饰的成员在类外不能直接被访问。
3、访问权限的作用域从该访问限定符出现位置到下一个访问限定符之间,如果没有下一个,作用域就直接到类的结尾。
4、class默认访问限定符是private,struct默认是public(因为要兼容C语言),如果类不写访问限定符,就使用默认的。
class Student
{
public:
void Init(const char* name, int ID, int age)
{
strcpy(_name, name);
_ID = ID;
_age = age;
}
void A()
{
Print();
}
protected:
void Print()
{
cout << _name << " " << _ID << " " << _age << endl;
}
private:
char _name[20];
int _ID;
int _age;
};
int main()
{
Student s;
s.Init("张三", 123, 12);
s.A();//通过调用A()来调用保护成员函数Print(),私有同理
return 0;
}
封装
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装在日常生活中就有很常见的例子,例如火车站:之所以建造火车站,就是为了便于管理乘客上下车,如果不封装管理,那么上下车无序就会出现印度火车的情况。
C++中也是如此,为了便于管理,对外只提供接口供你使用,对内提供数据。
5、类的作用域
类通过封装定义了一个新的作用域,花括号内就是其作用域范围。在类体外定义成员时,都与要通过作用域解析符 :: 指明该成员属于哪一个作用域。
class Student
{
public:
void Init(const char* name, int ID, int age);
private:
char _name[20];
int _ID;
int _age;
};
//指明Init函数属于Student类域
void Student::Init(const char* name, int ID, int age)
{
strcpy(_name, name);
_ID = ID;
_age = age;
}
6.类的实例化和类的大小
用类创建对象的过程,就叫做类的实例化
类相当于模具,一个类可以实例化多个对象,这些对象的成员属性不同,但是都能使用使用同一个成员函数。
所以可以得出结论:类的大小就是成员变量的大小,不包括成员函数,成员函数都保存在公共代码段
class Student
{
public:
void Init(const char* name, int ID, int age)
{
strcpy(_name, name);
_ID = ID;
_age = age;
}
void Print()
{
cout << _name << " " << _ID << " " << _age << endl;
}
private:
char _name[10];
int _ID;
int _age;
};
int main()
{
Student a;
a.Init("张三", 111, 16);
a.Print();
Student b;
b.Init("李四", 222, 17);
b.Print();
cout << "类的大小:" << sizeof(Student) << endl;
cout << "a的大小:" << sizeof(a) << endl;
return 0;
}
可以看到类和对象a的大小都只有20字节,明显没有存储成员函数;类的大小的计算和结构体一样,同样遵循结构体内存对齐规则。
猜测一下下面几个类的大小:
//有成员变量和成员函数
class A
{
public:
void Func1()
{}
private:
int a;
};
//仅有成员函数
class B
{
public:
void Func2()
{}
};
//空类
class C
{};
结论:一个类的大小,就是该类成员变量遵循内存对齐规则计算得到的和,如果是空类或没有成员变量的类,一定需要给1个字节来标记该类;因为当这种类实例化多个对象后,当你要访问其中一个对象的时候,就需要有一个独一无二的地址,所以编译器通常会给空类隐含的加一个字节用于辨识。
7、this指针
this指针的使用
class Student
{
public:
void Init(const char* name, int ID, int age)
{
strcpy(_name, name);
_ID = ID;
_age = age;
}
void Print()
{
cout << _name << " " << _ID << " " << _age << endl;
}
private:
char _name[10];
int _ID;
int _age;
};
int main()
{
Student a;
a.Init("张三", 111, 16);
a.Print();
Student b;
b.Init("李四", 222, 17);
b.Print();
return 0;
}
这个学生类中有两个成员函数,当不同对象调用同一个函数时,系统是如何区分的呢?
因此C++中通过引入 this 指针解决了这种问题:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
class Student
{
public:
void Init(const char* name, int ID, int age)
{
strcpy(this->_name, name);
this->_ID = ID;
this->_age = age;
}
void Print(Student* s)
{
cout << "调用对象的地址:" << s << endl;
cout << "this指针指向的目标地址:" << this << endl;
cout << this->_name << " " << this->_ID << " " << this->_age << endl;
}
private:
char _name[10];
int _ID;
int _age;
};
int main()
{
Student a;
a.Init("张三", 111, 16);
a.Print(&a);
cout << endl;
Student b;
b.Init("李四", 222, 17);
b.Print(&b);
return 0;
}
可以看到上面就是this指针的显示调用,this指针指向的就是调用对象的地址。
this指针的特性
1. this指针的类型:类类型* const。
2. 只能在“成员函数”的内部使用。
3. this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给 this 形参。所以对象中不存储 this 指针。
4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
如果不想让this指针作为第一个参数,就可以将成员函数定义成静态成员函数,因为静态成员函数不包含this指针
class Func
{
public:
void Init(int a)
{
_A = a;
}
static void Print1()//静态成员函数
{
//cout << _A << endl;//err
cout << _B << endl;
}
void Print2() //非静态成员函数
{
cout << _A << endl;
cout << _B << endl;
}
private:
int _A; //非静态成员变量
static int _B;//静态成员变量
};
int Func::_B = 10;
int main()
{
Func a;
a.Init(20);
a.Print1();//10
a.Print2();//20 10
return 0;
}
静态成员函数只能访问静态成员变量,非静态成员函数没有限制;静态成员变量必须在类外初始化