前言
C++为啥要引入类这个概念呢,以C实现栈为例说明。
typedef int STDataType;
typedef struct Stack
{
. . . .int top;
. . . .STDataType* data;
. . . .int capacity;
}Stack;
void StackInit(Stack* ps); //栈的初始化
void StackDestory(Stack* ps); //栈的销毁
void StackPush(Stack* ps, STDataType x); //入栈
void StackPop(Stack* ps); //出栈
STDataType StackTop(Stack* ps); //栈顶
假设C++大佬带了几个实习生,结果实习生这样获取栈顶元素
Stack st;
printf("%d ", st.data[st.top]);
结果的得不到栈顶元素,反而得到的是一个随机值,实习生就会跑过来问大佬了,这个栈不好用啊我这怎么没得到栈顶元素啊,反而得到一个随机值。大佬给他解释半天栈只能通过调用给你的接口来实现…第一个实习生听懂了,过两天又有个实习生跑过来问这个问题,大佬有耐心给他解释,过几天又有个实习生跑过来问这个问题,问的人多了,大佬就开始反思了,C这个语言有缺陷,能不能在C基础上解决这个问题呢于是对C进行了强化命名C++,引入类,来更好的封装,干脆把成员变量保护起来不让用,只能调用我的接口来实现。
1.面向过程和面向对象初步认识
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
2.类的引入
C语言中,结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。 在c++中struct升级成了类,类和结构体不同的是类除了可以定义变量,还可以定义方法/函数。
3.类的定义
//struct className
class className
{// 类体:由成员函数和成员变量组成}; // 一定要注意后面的分号
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号。类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。
也可以用struct定义类,二者区别是C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。和class是定义类是一样的,区别是struct的成员默认访问方式是public,class是struct的成员默认访问方式是private。
类的两种定义方式:
1. 声明和定义全部放在类体中,需要注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
class student
{
public://(公有的) --- 参考下面访问限定符讲解void StudentInit(const char* name, const char* gender, int age){strcpy(_name, name);strcpy(_gender, gender);_age = age;}void printf(){cout << _name << " "<< _gender << " "<< _age << endl;}
private://(私有的)char _name[20];char _gender[20];int _age;
};
2. 声明放在.h文件中,类的定义放在.cpp文件中
.h 文件中
class student
{
public://(公有的)void StudentInit(const char* name, const char* gender, int age);void printf();
private://(私有的)char _name[20];char _gender[20];int _age;
};
.cpp文件中
void student::StudentInit(const char* name, const char* gender, int age){strcpy(_name, name);strcpy(_gender, gender);_age = age;}void student::printf(){cout << _name << " "<< _gender << " "<< _age << endl;}
一般情况下,更期望采用第二种方式。
4.访问限定符
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
三种访问限定符:public(公有),private(私有),protected(保护)
【访问限定符说明】
- public修饰的成员在类外可以直接被访问
- protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- class的默认访问权限为private,struct为public(因为struct要兼容C)
5.类的大小
我们通过一组程序来计算类的大小:
//类中既有成员变量,又有成员函数
class student1
{
public:void StudentInit(const char* name, const char* gender, int age){}void printf(){}
private://(私有的)int _age;
};//类中仅有成员函数
class student2
{
public:void StudentInit(const char* name, const char* gender, int age){}void printf(){}
};
//类中什么都没有 -- 空类
class student3
{
};int main()
{cout << sizeof(student1) <<endl;cout << sizeof(student2) <<endl;cout << sizeof(student3) <<endl;return 0;
}
运行结果:
由此可见一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类。
6.this指针
对于类有这样一个问题:student类中有StudentInit与printf两个成员函数,函数体中没有关于不同对象的区分,那当s1调用printf函数时,该函数是如何知道应该设置s1对象,而不是设置s2对象呢?
C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
拿代码说明:
class student
{
public:void StudentInit(const char* name, const char* gender, int age){strcpy(_name, name);strcpy(_gender, gender);_age = age;}//void printf(student* const this) --- 定义成员函数时不能显示声明形参thisvoid printf(){cout << _name << " "<< _gender << " "<< _age << endl;//cout << this->_name << " " <<this->_gender << " " << this->_age << endl;//成员函数内部可以这样用,但通常不这样用}
private://(私有的)char _name[20];char _gender[20];int _age;
};
int main()
{student s1;student s2;s1.StudentInit("张三", "男", 20); //s.StudentInit(&s,"张三", "男", 20);s2.StudentInit("李四", "男", 10);s1.printf(); //s.printf(&s) --- 调用成员函数时,不能显示传实参给thiss2.printf();return 0;
}
this指针特性:
- this指针的类型:类类型* const
- 只能在“成员函数”的内部使用
- this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
- this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
- this指针一般存在栈中(形参),vs是存在寄存器中的。
以下两篇文章和本文连贯性较强,建议一起看。点下面文字即可跳转对应文章。
构造函数和析构函数
拷贝构造函数与运算符重载