1. 对象
1.1 为什么要面向对象
1.2 什么是面向对象
2. 类
- 从语法角度来说类是自定义的数据类型,类似之前所说的结构体,但比结构体要复杂。成员变量和成员函数表达对象的属性和行为。
- 上图中左边是一群狗,编程时把他们共同的属性和行为提取出来就得到了类。然后再用类实例化出具体的对象,得到右边各种各样的狗。
2.1 类的一般形式
- 定义类 (原来叫定义结构体, 现在叫定义类) 一般都用class, 保留struct是为了兼容c中的结构体。
2.1.1 代码示例
#include <iostream>
using namespace std;
// 定义一个类:Student
struct Student {
// string 这个类定义的对象比较大, 这里加&提高传参效率,避免实参到形参的拷贝
// const是为了再提高传参效率的同时,还可以接收常量型的实参(右值?)
void eat(const string& food){
cout << "i am eating " << food <<endl;
}
// int 只有4个字节,加不加&都不会太影响传参效率
void sleep(int hour) {
cout << "i have slpeeped " << hour << endl;
cout << m_age << endl;
}
// who 属于类中的一个成员函数,可以直接访问成员函数
void who(void) {
cout << "my name is " << m_name << ", i am " << m_age << " years old, "
<< "my id is " << m_no << endl;
}
string m_name;
int m_age;
int m_no;
};
int main(void){
// 用 Student 类 实例化一个对象
Student s;
s.m_name = "xuehui";
s.m_age = 25;
s.m_no = 666;
s.who();
s.eat("rice");
s.sleep(8);
return 0;
}
$ ./a.out
my name is xuehui, i am 25 years old, my id is 666
i am eating rice
i have slpeeped 8
2.2 访问控制限定符
2.2.1 代码示例
#include <iostream>
using namespace std;
// 使用class定义一个类:Student
// 它的缺省访问控制属性是private, 只有类内部才可以访问类的成员。
class Student {
// 访问控制属性改成public以后, 下面的main函数中才能直接访问对象s的成员变量
public:
// string 这个类定义的对象比较大, 这里加&提高传参效率,避免实参到形参的拷贝
// const是为了再提高传参效率的同时,还可以接收常量型的实参(右值?)
// 在类的外面可以直接调用 类中公有成员函数 setName
// 来访问私有成员变量 name。 这就是封装
// 对非法数据加以限定,增加的程序的安全以及逻辑合理性。
void setName(const string& name){
if (name == "sb"){
cout << "you are sb!" << endl;
}
else
{
m_name = name;
}
}
private:
string m_name;
};
int main(void){
// 用 Student 类 实例化一个对象 s
Student s;
s.setName("sb");
return 0;
}
$ ./a.out
you are sb!
2.3 构造函数
- 使用构造函数是为了在创建对象的时候希望有一个确定的初始化状态。
- 可以把构造函数理解为创建对象时一定会被调用的一个成员函数。一定会被调用,且只会被调用一次。
2.3.1 代码示例
#include <iostream>
using namespace std;
// 使用class定义一个类:Student
// 它的缺省访问控制属性是private, 只有类内部才可以访问类的成员。
class Student {
// 访问控制属性改成public以后, 下面的main函数中才能直接访问对象s的成员变量
public:
// string 这个类定义的对象比较大, 这里加&提高传参效率,避免实参到形参的拷贝
// const是为了再提高传参效率的同时,还可以接收常量型的实参(右值?)
Student(const string& name){
cout << "constructor" << endl;
m_name = name;
}
void who(void) {
cout << "my name is " << m_name << endl;
}
private:
string m_name;
};
int main(void){
// 用 Student 类 实例化一个对象 s, 期间会自动调用构造函数
// 所以要指明构造函数需要的实参
Student s("xuehui");
s.who();
// s.Student("test"); 构造函数不能显示调用
return 0;
}
$ ./a.out
constructor
my name is xuehui
2.3.2 用类实现一个时钟
#include <iostream>
#include <ctime>
#include <cstdio>
#include <unistd.h>
using namespace std;
class Clock {
public:
// 构造函数初始化当前时间. 用到的函数:man 2 time, man 3 localtime
Clock(time_t t){
// 定义tm结构体类型变量相比C直接省略struct
tm* local = localtime(&t);
m_hour = local->tm_hour;
m_min = local->tm_min;
m_sec = local->tm_sec;
}
// 自动刷新时间
void run(void){
while(1){
// \r 光标停在行首。%02 站两位,不够前面补零。
printf("\r%02d:%02d:%02d",m_hour, m_min, m_sec);
// 因为上面没有\n, 不会立马输出
// fflush 刷新标准输出缓冲区,上面printf的内容会立马输出到屏幕
fflush(stdout);
if(++m_sec == 60){
m_sec = 0;
if(++m_min == 60){
m_min = 0;
if(++m_hour == 24){
m_hour = 0;
}
}
}
sleep(1);
}
}
private:
int m_hour;
int m_min;
int m_sec;
};
int main(void){
Clock c(time(NULL));
c.run();
return 0;
}
$ ./a.out
12:35:06
3. 对象的创建和销毁
3.1 在栈区创建对象
- 类名 对象 = 类名(实参表): 这是拷贝初始化的写法,用实参表先构造一个匿名对象,再对=左边的对象进行拷贝初始化,然后再把匿名对象释放掉。(有点类似临时变量)
3.1.2 代码示例
#include <iostream>
using namespace std;
class Student {
public:
// 构造函数的重载
Student(void){
cout << "constructor_1" << endl;
m_name = "x";
}
Student(const string& name){
cout << "constructor_2" << endl;
m_name = name;
}
void who(void) {
cout << "my name is " << m_name << endl;
}
private:
string m_name;
};
int main(void){
// 1. 在栈区创建单个对象
// 1.1 以无参方式创建对象
Student s1; // Student s1(); 不可以这样写, 编译器会把s1当成返回值是Student类型的函数
s1.who();
// 1.2 以有参方式创建对象
Student s2("xuehui");
s2.who();
// 1.3 另一种写法: 拷贝初始化
Student s3 = Student("xuehui");
s3.who();
// 2. 在栈区创建对象数组
// 2.1 无参方式
Student sarr1[1];
sarr1[0].who();
// 2.2 有参方式
Student sarr2[2] = {Student("xuehui"), Student("xuehui_1")};
sarr2[0].who();
sarr2[1].who();
return 0;
}
$ ./a.out
constructor_1
my name is x
constructor_2
my name is xuehui
constructor_2
my name is xuehui
constructor_1
my name is x
constructor_2
constructor_2
my name is xuehui
my name is xuehui_1
3.2 在堆区创建/销毁对象
- new 在堆区完成内存分配,还会自动去调用构造函数,完车对象的初始化。而malloc只会完成内存分配。
- 同样的如果delete的是一个对象类型的指针,除了会完成动态资源的释放,它还会去调用这个对象对应类的析构函数。把这个对象的维护的动态资源释放掉。而 free只是会完成动态资源的释放。
#include <iostream>
using namespace std;
class Student {
public:
// 构造函数的重载
Student(void){
cout << "constructor_1" << endl;
m_name = "x";
}
Student(const string& name){
cout << "constructor_2" << endl;
m_name = name;
}
void who(void) {
cout << "my name is " << m_name << endl;
}
private:
string m_name;
};
int main(void){
// 1. 在堆区创建单个对象
// 1.1 无参方式
Student* ps1 = new Student;
ps1->who();
delete ps1;
// 1.2 有参方式
Student* ps2 = new Student("xuehui");
ps2->who();
delete ps2;
// 2. 在堆区创建多个对象
// 2.1 无参方式
// parr 相当于数组名,指向数组第一个元素
Student* parr1 = new Student[2];
(parr1+0)->who();
(parr1+1)->who();
delete [] parr1;
// 2.2 有参方式
Student* parr2 = new Student[2]{Student("xuehui_1"), Student("xuehui_2")};
(parr2+0)->who();
(parr2+1)->who();
delete [] parr2;
return 0;
}
$ ./a.out
constructor_1
my name is x
constructor_2
my name is xuehui
constructor_1
constructor_1
my name is x
my name is x
constructor_2
constructor_2
my name is xuehui_1
my name is xuehui_2
4. 多文件编程
4.1 类的声明、实现 可以分开写
Student.h
#ifndef __STUDNET__
#define __STUDNET__
#include <iostream>
using namespace std;
class Student {
public:
Student(const string& name);
void who(void);
private:
string m_name;
};
#endif // __STUDNET__
Student.cpp
#include "Student.h"
// 需要在函数名前加上"类名:",指明它是类中的成员函数
Student::Student(const string& name){
m_name = name;
}
void Student::who(void) {
cout << "my name is " << m_name << endl;
};
main.cpp
#include "Student.h"
int main(void){
Student s("xuehui");
s.who();
return 0;
}
$ g++ main.cpp Student.cpp
$ ./a.out
my name is xuehui