一、概述
1、什么是对象
(1)万物皆对象。
(2)程序就是一组对象,对象之间通过消息交换信息。
(3)类是对对象的描述和抽象,对象是类的具体化和实例化。
2、通过类描述对象
类是从属性和行为两个方面对对象进行抽象的。
属性:姓名、年龄、学号。
行为:吃饭、睡觉、学习。
3、面向对象程序设计(OOP)
(1)精通一种面向对象的元语言 —— UML。
(2)研究设计模式——GOF。
二、类的基本语法
1、类的定义
class 类名{};
1.1、成员变量 —— 属性
class 类名{
类型 成员变量名;
};
1.2、成员函数 —— 行为
类名{
返回类型 成员函数名(形参表){
函数体
}
};
如:
class Student{
string m_name;
int m_age;
void eat(const string& food){
...
}
};
2、访问控制属性
(1)公有成员:public,谁都可以访问。
(2)私有成员:private,只有自己可以访问。
(3)保护成员:protected,只有自己和自己的子类可以访问。
(4)类的成员缺省访控属性为私有,而结构体的成员缺省访控属性为公有。
#include <iostream>
using namespace std;
class Student{
private:
string m_name;
int m_age;
public:
void eat(const string& food){
cout << m_age << "岁的" << m_name << "同学正在吃" << food <<"。" << endl;
}
void setName(const string& name){
if (name == "2")
cout << "你才" << name << "!" << endl;
else
m_name = name;
}
void setAge(int age){
if (age < 0)
cout << "无效的年龄!" << endl;
else
m_age = age;
}
};
int main(){
Student student;
student.setName("张飞");
student.setAge(25);
student.eat("包子");
return 0;
}
3、构造函数
class 类名{
...
类名(形参表){
构造函数体;
}
};
当一个对象被创建时,构造函数会自动被执行,其参数来自构造实参。
(1)构造函数可以通过构造参数实现重载。
(2)如果一个类没有定义任何构造函数,那么系统就会缺省的为其提供一个无参构造函数,该构造函数对于基本类型的成员变量不做初始化,对于类类型的成员变量,调用其相应类型的无参构造函数初始化。
(3)对象的创建过程
分配内存 -> 调用构造函数
-> 调用类类型成员的构造函数 -> 构造函数的代码
#include <iostream>
using namespace std;
class Student{
private:
string m_name;
int m_age;
public:
void eat(const string& food){
cout << m_age << "岁的" << m_name << "同学正在吃" << food <<"。" << endl;
}
/*void _ZN7Student3eatERKSs(Student* this, const string& food){
cout << this->m_age << "岁的" << this->m_name << "同学正在吃" << food <<"。" << endl;
}*/
void setName(const string& name){
if (name == "2")
cout << "你才" << name << "!" << endl;
else
m_name = name;
}
void setAge(int age){
if (age < 0)
cout << "无效的年龄!" << endl;
else
m_age = age;
}
//构造函数
Student(const string& name, int age){
m_name = name;
m_age = age;
}
//无参构造
Student(){
m_name = "无名";
m_age = 0;
}
//单参构造
Student(const string& name) : m_name(name), m_age(0){}
};
int main(){
Student s1("张飞", 25);
s1.eat("包子");
//_ZN7Student3eatERKSs(&s1, "包子");
Student s2 = Student("赵云", 22);
s2.eat("馒头");
//_ZN7Student3eatERKSs(&s2, "包子");
Student s3;
s3.eat("烧饼");
Student* s4 = new Student("关羽", 26);//堆对象
s4->eat("油条");
delete s4;
Student& s5 = *new Student();
s5.eat("面条");
delete &s5;
Student sa1[3] = {s1, s2};
sa1[0].eat("KFC");
sa1[1].eat("KFC");
sa1[2].eat("KFC");
Student* sa2 = new Student[3]{s1, s2};//-std=c++0x
delete[] sa2;
Student s6("刘备");
s6.eat("米饭");
return 0;
}
4、初始化表
class 类名{
类名(...) : 初始化表{
构造函数体;
}
};
/*
const int x = 100;
x = 100;
int& a = b;
a = b;
*/
(1)如果类中含有常量或引用型的成员变量,必须通过初始化表对其初始化。
(2)成员变量的初始化顺序仅与其被声明的顺序有关,而与初始化表的顺序无关。
class A{
public:
A(char* psz) : m_str(psz), m_len(m_str.length()){}/*因为m_len先声明,故先初始化,但在初始化
m_len时,要用到m_str的长度,但m_str现在还不存在,故会出错*/
private:
size_t m_len;
string m_str;
};
//正确的做法是在初始化表中减少变量间的相互耦合,这里把m_str.length()改为strlen(psz)。
#include <iostream>
using namespace std;
int g_data;
class A{
private:
const int m_c;
int& m_r;
public:
A() : m_c(100), m_r(g_data){
//m_c = 100;
//m_r = g_data;
}
void print(void){
cout << m_c << ' ' << m_r << endl;
}
};
int main(){
A a;
a.print();
return 0;
}
练习:实现一个时钟,有两种工作模式:一种是计时器;一种是显示当前系统时间。
#include <iostream>
#include <iomanip>
#include <unistd.h>
using namespace std;
class Clock{
private:
short sec;
short min;
short hou;
void show(){
cout << '\r' << setfill('0')
<< setw(2) << hou << ':'
<< setw(2) << min << ':'
<< setw(2) << sec << flush;
//printf("\r%02d:%02d:%02d", hou, min, sec);
}
void tick(){
sleep(1);
if (++sec == 60){
sec = 0;
if (++min == 60){
min = 0;
if (++hou == 24){
hou = 0;
}
}
}
}
public:
Clock(bool timer = true) : hou(0), min(0), sec(0){
if (!timer){
time_t t = time(NULL);
tm* local = localtime(&t);
hou = local->tm_hour;
min = local->tm_min;
sec = local->tm_sec;
}
}
void run(){
while (1){
show();
tick();
}
}
};
int main(){
Clock clock;
//Clock clock(false);
clock.run();
return 0;
}
5、析构函数
class 类名{
~类名(void){
析构函数体;
}
};
当一个对象被销毁时,自动执行析构函数。局部对象离开作用域时被销毁,堆对象delete时被销毁。
如果一个类没有定义任何析构函数,那么系统会提供一个缺省析构函数。缺省析构函数对基本类型的成员变量什么也不干,对类类型的成员变量,调用相应类型的析构函数。
一般情况下,在析构函数中释放各种动态分配的资源。
构造:基类-》成员-》子类
析构:子类-》成员-》基类
#include <iostream>
using namespace std;
class Double{
public:
Double(double data) : m_data(new double(data)){
cout << "构造" << endl;
}
~Double(){
delete m_data;
cout << "析构" << endl;
}
void print() const{
cout << *m_data << endl;
}
private:
double* m_data;
};
int main(){
{
Double d1(3.14);
d1.print();
}
Double* d2 = new Double(1.23);
delete d2;
cout << "再见!" << endl;
return 0;
}
6、this指针
(1)一般而言,在类的构造函数或成员函数中,关键字this表示一个指针,对于构造函数而言,this指向正在被构造的对象,对于成员函数而言,this指向调用该函数的对象。
(2)this指针的用途
A、在类的内部区分成员变量。
#include <iostream>
using namespace std;
class A{
public:
int data;
A(int data){
cout << "构造:" << this << endl;
this->data = data;
}
void foo(void){
cout << "foo:" << this << endl;
cout << this->data << endl;
}
};
int main(){
A a(100);
cout << "main:" << &a << endl;
a.foo();
A* pa = new A(100);
cout << "main:" << pa << endl;
pa->foo();
return 0;
}
B、在成员函数中返回调用对象自身。
#include <iostream>
using namespace std;
class Counter{
public:
Counter() : m_data(0){}
Counter& inc(){
++m_data;
return *this;
}
void print(){
cout << m_data << endl;
}
private:
int m_data;
};
int main(){
Counter c;
c.inc().inc().inc();
c.print();//3
return 0;
}
C、在成员函数内部通过参数向外界传递调用对象自身,以实现对象间交互。
#include <iostream>
using namespace std;
class Student;
class Teacher{
public:
void educate(Student* s);
void reply(const string& answer){
m_answer = answer;
}
private:
string m_answer;
};
class Student{
public:
void ask(const string& question, Teacher* t){
cout << "问题:" << question << endl;
t->reply("不知道。");
}
};
void Teacher::educate(Student* s){
s->ask("什么是this指针?", this);
cout << "答案:" << m_answer << endl;
}
int main(){
Teacher t;
Student s;
t.educate(&s);
return 0;
}
7、常函数与常对象
(1)如果在一个类的成员函数的参数表后面加上const关键字,那么这个成员函数就被称为常函数,常函数的this指针是一个常指针。在常函数内部无法修改成员变量,除非该变量具有mutable属性。而且在常函数内部也无法调用非常函数。
(2)常对象:拥有const属性的对象、对象引用或指针。
注意:
A、常对象只能调用常函数。
B、同型的常函数和非常函数可以构成重载关系。常对象调用常版本,非常对象调用非常版本。如果没有非常版本,非常对象也可以调用常版本。
const XXX 函数名(const YYY yyy) const{
...
}
//第三个const修饰this指针,所以全局函数不能是常函数。
#include <iostream>
using namespace std;
class A{
public:
void bar(){
cout << "非常bar" <<endl;
}
//void XXXbarYYY(A* this){}
void bar() const{
cout << "常bar" << endl;
}
void foo() const{
//m_i = 100;
const_cast<A*> (this)->m_i = 100;//mutable关键字的作用就是去常,这里自己去常
}
/*_ZNK1A3fooEv(const A* this){
const_cast<A*> (this)->m_i = 100;
}*/
void print() const{
cout << m_i << endl;
}
int m_i;
//mutable int m_i;
};
int main(){
A a;
a.foo();
a.print();
const A& r = a;
r.bar();
//XXXbarYYY(&r);//const A*
a.bar();
//XXXbarYYY(&a);//A*
return 0;
}