在步入研究生生涯以来,痛感自身编程知识与能力的不足,亟需重新回顾,并重点学习关于C++中面向对象、类、STL等关键知识,提高自身编程能力,故决定开启关于C++编程知识的专栏以供参考,希望能够起到有效的作用。注:本系列教程主要参考了C语言中文网、菜鸟教程以及部分CSDN博主的相关内容,无任何商业用途。
一个类可以创建多个对象,每个对象都是类类型的一个变量;创建对象的过程也叫类的实例化,每个对象都是类的一个具体实例。
类的定义意味着什么:定义一个类,本质上是定义一个数据类型的蓝图。这实际上并没有定义任何数据,但它定义了类的名称意味着什么,也就是说,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。
类需要被定义:类是用户自定义的类型,如果程序中要用到类,必须提前说明,或者使用已存在的类(别人写好的类、标准库中的类等),C++语法本身并不提供现成的类的名称、结构和内容。
类如何定义:
- 以关键字 class开头,后跟类的名称
\qquad (1)class
:C++新增的关键字,专门用来定义类
\qquad (2)如下面的例子中的Student
:类的名称,首字符通常大写,与其他标识符区分- 类的主体是包含在一对花括号 { } 中
\qquad (1)成员变量:或者说类的属性(Property)
\qquad (2)成员函数:或者说类的方法(Method)- 分号或声明列表:分号同样是类定义的一部分,表示类定义结束了,不能省略。
- 类通常定义在函数外面
// 类的定义
class Student{
public:
//成员变量
char *name;
int age;
float score;
//成员函数
void say(){
cout<
- 类似于基本类型定义变量:
Student Caiyanxin; // 创建对象
- 创建对象时
class
关键字可加可不加,通常省略:class Student Caiyanxin; //正确 Student Caiyanxin; //同样正确
- 创建对象数组:数组中的每个元素都是对应类的对象
Student bad_student[100];
重点:创建对象的两种方式:栈/堆
Student stu;
Student *pStu = &stu;
&
获取地址,并通过指针指向该对象; Student *pStu = new Student;
new
创建的对象在堆上分配内存,此时这个对象是匿名的,无法直接使用,必须使用一个指针变量类接收一个指向该对象的指针,通过指针访问对应成员1. 使用
.
点号访问:类似于结构体变量访问成员// 创建对象 Student Caiyanxin; // 访问对象成员 Caiyanxin.name = "caiyanxin"; Caiyanxin.age = 22; Caiyanxin.score = 98.5f;
2. 使用对象指针:通过
->
来访问,类似于结构体指针访问// 创建对象 Student *pStu = new Student; // 访问对象成员 pStu -> name = "winter"; pStu -> age = 18; pStu -> score = 99.5f; pStu -> say(); delete pStu;
函数类型\属性 | 独立性 | 作用范围 |
---|---|---|
类成员函数 | 类的成员,出现在类体中 | 由类决定 |
普通函数 | 独立的 | 全局/某个命名空间 |
- 在类体内定义成员函数:默认自动成为内联函数,无需使用inline标识符,不需要在函数名前面加上类名
- 在类体外定义成员函数:必须在函数名前面加上类名限定,再使用范围解析运算符/域解析符
::
来连接类名和函数名,指明当前函数属于哪个类
class Student{
public:
//成员变量
char *name;
int age;
float score;
//成员函数
void say(); //函数声明
};
//函数定义
void Student::say(){
cout<
不推荐在类体外定义inline函数的原因:
- 在类外定义内联函数必须将类的定义和成员函数的定义都放在同一个头/源文件中,否则编译时无法进行嵌入(将函数代码的嵌入到函数调用处)
- 对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。
- 只有当函数只有 10 行甚至更少时才将其定义为内联函数,为了用空间代价换区时间效率
- 在内联函数内不允许使用循环语句和开关语句;
- 内联函数的定义必须出现在内联函数第一次调用之前;
- 类结构中所在的类说明内部定义的函数是内联函数。
- 如果已定义的函数多于一行,编译器会**忽略 inline **限定符
public
、protected
、private
,三个关键字来控制成员变量和成员函数的访问权限,分别表示公有的、受保护的、私有的公有成员 public:
概念:在程序中类的外部可访问,可以不是用任何成员函数来设置和获取公有变量的值
实际操作特点:在公有区域定义相关的函数,方便在类的外部可以调用这些函数
私有成员 private:
概念:在程序中类的外部不可访问,且不可查看,只有类和友元函数可以访问私有成员
特性:在默认情况下类的所有成员都是私有的
实际操作特点:在私有区域定义数据,防止数据在外部修改和泄露,常常将成员变量以m_
开头,与成员函数中参数区别开来
受保护成员 protected:
- 修饰符的修饰成员次序任意,既可以先写private部分,也可以先写public部分
- 一个类体中一种修饰符可出现多次,每个部分的有效范围到出现到另一访问修饰符或类体结束时为止
- 实际编程中为使程序清晰,应该使每一种修饰符在类体中只出现一次
#include
using namespace std;
class Box
{
public:
double length;
void setWidth( double wid );
double getWidth( void );
private:
double height;
protected:
double width;
};
// 成员函数定义
double Box::getWidth(void)
{
return width ;
}
void Box::setWidth( double wid )
{
width = wid;
}
// SmallBox 是派生类
class SmallBox:Box
{
public:
void setSmallWidth( double wid );
double getSmallWidth( void );
};
// 子类的成员函数
double SmallBox::getSmallWidth(void)
{
return width ;
}
void SmallBox::setSmallWidth( double wid )
{
width = wid;
}
// 程序的主函数
int main( )
{
Box box;
// 不使用成员函数设置长度
box.length = 10.0; // OK: 因为 length 是公有的
cout << "Length of box : " << box.length <
静态成员变量:
目的/本质特性:多个对象共享数据,即无论创建多少个对象,静态成员变量都只有一个副本
创建:使用关键字static
修饰
关键特性:
- static 成员变量属于类,不属于某个具体的对象,创建多个对象时也只分配一份内存;
- static 成员变量必须在类声明的外部初始化,且如果不赋值,那么会被默认初始化为0
- 静态成员变量在初始化时不能再加 static,但必须要有数据类型。被
private、protected、public
修饰的静态成员变量都可以用这种方式初始化。- static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。反过来说,没有在类外初始化的 static 成员变量不能使用。
- static 成员变量既可以通过对象来访问,也可以通过类来访问,但要遵循
private、protected、public
关键字的访问权限限制。当通过对象名访问时,对于不同的对象,访问的是同一份内存。- static 成员变量不占用对象的内存,而是在所有对象之外开辟内存,即使不创建对象也可以访问
- static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放。static 成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。
- 全局数据区的变量都有默认的初始值 0,而动态数据区(堆区、栈区)变量的默认值是不确定的。
静态成员函数:
static
修饰#include
using namespace std;
class Student{
public:
Student(char *name, int age, float score);
void show();
public: //声明静态成员函数
static int getTotal();
static float getPoints();
private:
static int m_total; //总人数
static float m_points; //总成绩
private:
char *m_name;
int m_age;
float m_score;
};
int Student::m_total = 0;
float Student::m_points = 0.0;
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){
m_total++;
m_points += score;
}
void Student::show(){
cout< m_total = 20;
// 使用匿名函数
(new Student("小明", 15, 90.6)) -> show();
(new Student("李磊", 16, 80.5)) -> show();
(new Student("张华", 16, 99.0)) -> show();
(new Student("王康", 14, 60.8)) -> show();
int total = Student::getTotal();
float points = Student::getPoints();
cout<<"当前共有"<
const成员:
const
加以限定
- 用法:与普通const变量相似,只需要在声明时加上 const 关键字;
- 初始化const成员变量:通过构造函数的初始化列表进行,具体可见下篇博客;
- 用法:必须在声明和定义的时候在函数头部的结尾加上 const 关键字
- 通常将 get 函数设置为常成员函数。读取成员变量函数的名字通常以get开头,后跟成员变量的名字
class Student{
public:
Student(char *name, int age, float score);
void show();
//声明常成员函数
char *getname() const;
int getage() const;
float getscore() const;
private:
char *m_name;
int m_age;
float m_score;
};
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
void Student::show(){
cout<
例如:const char * getname()
例如:char * getname() const
const对象:
// 定义常对象
const class object(params);
class const object(params);
// 定义const指针
const class *p = new class(params);
class const *p = new class(params);
// class为类名,object为对象名,params为实参列表,p为指针名
友元函数:
用法:使用friend
关键字修饰,可以是不属于任何类的非成员函数(类以外定义),也可以是其他类的成员函数,都可以在类中声明;
作用:友元函数可以访问当前类中的所有成员,包括 public、protected、private 属性;
\qquad (1)将非成员函数声明为友元函数:
#include
using namespace std;
class Student{
public:
Student(char *name, int age, float score);
public:
friend void show(Student *pstu); //将show()声明为友元函数
private:
char *m_name;
int m_age;
float m_score;
};
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
//非成员函数
void show(Student *pstu){
cout<m_name<<"的年龄是 "<m_age<<",成绩是 "<m_score<
- 上述代码中
show()
函数为全局范围的非成员函数,不属于类,需要调用student类中的private成员,故需要声明为友元函数- 友元函数不同于类的成员函数,在友元函数中不能直接访问类的成员,必须要借助对象
- 成员函数在调用时会隐式地增加 this 指针,指向调用它的对象,从而使用该对象的成员;而 show() 是非成员函数,没有 this 指针,编译器不知道使用哪个对象的成员,要想明确这一点,就必须通过参数传递对象(可以直接传递对象,也可以传递对象指针或对象引用),并在访问成员时指明对象。
\qquad (2)将其他类成员函数声明为友元函数:
#include
using namespace std;
class Address; //提前声明Address类
//声明Student类
class Student{
public:
Student(char *name, int age, float score);
public:
void show(Address *addr);
private:
char *m_name;
int m_age;
float m_score;
};
//声明Address类
class Address{
private:
char *m_province; //省份
char *m_city; //城市
char *m_district; //区(市区)
public:
Address(char *province, char *city, char *district);
//将Student类中的成员函数show()声明为友元函数
friend void Student::show(Address *addr);
};
//实现Student类
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
void Student::show(Address *addr){
cout<m_province<<"省"<m_city<<"市"<m_district<<"区"< show(paddr);
return 0;
}
- 由于在address类定义前使用了该类,故需要提前声明
- 一般情况下类必须要在正式声明后使用,但是有些时候提前声明也可以先使用,但是只有在正式声明之后才能创建对象(内存原因)
- 一个函数可以被多个类声明为友元函数,就可以访问多个类中的 private 成员
友元类:
- private: 实际项目开发中的成员变量以及只在类内部使用的成员函数
(只被成员函数调用的成员函数,对外部无影响)- public:允许通过对象调用的成员函数
赋值思想:额外添加两个 public 属性的成员函数,set函数用于给成员变量赋值,get函数用于读取成员变量的值
初始化成员变量:创建对象时调用构造函数
概念:一个const指针,指向当前对象,是所有成员函数的隐含参数
用处:在成员函数内部,它可以用来指向调用对象从而访问对象的所有成员,而每一个对象都能通过 this 指针来访问自己的地址
#include
using namespace std;
class Student{
public:
void setname(char *name);
void setage(int age);
void setscore(float score);
void show();
private:
char *name;
int age;
float score;
};
void Student::setname(char *name){
this->name = name;
}
void Student::setage(int age){
this->age = age;
}
void Student::setscore(float score){
this->score = score;
}
void Student::show(){
cout<name<<"的年龄是"<age<<",成绩是"<score< setname("李华");
pstu -> setage(16);
pstu -> setscore(96.5);
pstu -> show();
return 0;
}
- 只能用在类的内部,通过 this 可以访问类的所有成员(包含私有成员)
- 可用于区分成员变量和重名的函数参数
- this 是一个指针,要用
->
来访问成员变量或成员函数- 只有在对象被创建以后才会给 this 赋值,并且这个赋值的过程是编译器自动完成的,不需要用户干预,用户也不能显式地给 this 赋值,因此不能在 static 成员函数中使用
- this 是 const 指针,它的值是不能被修改的,一切企图修改该指针的操作,如赋值、递增、递减等都是不允许的。
- this 只能在成员函数内部使用,用在其他地方没有意义,也是非法的
关键字 | 成员默认属性 | 默认继承方式 | 模板是否可用 |
---|---|---|---|
class | private | private | 可以 |
struct | public | public | 不可以 |
内容: 本篇博客主要介绍了C++中面向对象概念的关键:类与对象,其中详细的介绍了类的基本概念、定义方法以及对象的使用,并且从成员函数与成员变量的概念、访问权限出发,提及了内联函数、静态成员、const成员与友员函数/类的使用方法,最后对类的封装、this指针以及与结构体之间的区别,深入浅出地从各个角度分析了类的使用方法,在下篇博客中将会主要针对于构造函数、析构函数与拷贝的讲解,敬请期待