C++入门基础------类的介绍

本文是对C++的一些知识点总结以及自己的理解,建议是对于C有较好的理解或者是学过一些C++的同学使用,可以加深自己的理解!

一、 类的定义

类是创建对象的模板,一个类可以创建多个对象,每个对象都是类类型的一个变量;创建对象的过程也叫类的实例化。与结构体一样,类只是一种复杂数据类型的声明,不占用内存空间。而对象是类这种数据类型的一个变量,或者说是通过类这种数据类型创建出来的一份实实在在的数据,所以占用内存空间。
一个简单的类的定义:

class Student{
public:
    //成员变量
    char *name;
    int age;
    float score;
    //成员函数
    void say(){
        cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
    }
};

class是 C++ 中新增的关键字,专门用来定义类。Student是类的名称;类名的首字母一般大写,以和其他的标识符区分开。{ }内部是类所包含的成员变量和成员函数,它们统称为类的成员(Member);由{ }包围起来的部分有时也称为类体,和函数体的概念类似。还有一个public关键字,这个是表示公共的意思,后面会有介绍。
注意在类定义的最后有一个分号;,它是类定义的一部分,表示类定义结束了,不能省略。类可以理解为一种新的数据类型,该数据类型的名称是 Student。与 char、int、float 等基本数据类型不同的是,Student 是一种复杂数据类型,可以包含基本类型,而且还有很多基本类型中没有的特性。
类还有一个很重要的知识,就是构造和析构。其实这两个理解起来不难,构造的话就是,你在创建一个类对象的时候系统会自动调用的一个函数;析构则是。你释放一个对象的时候他会执行的函数。
这么听起来就很简单吧!其实就是两个函数,只是这两个函数不是我们自己调用的,而是系统自己调用的而已。我们以上面Student类为例创建一个构造和析构。

class Student{
public:
	//构造函数
	Student(){
	cout<<”这是构造”<<endl;
}
	//析构函数
	~ Student(){
	cout<<”这是析构”<<endl;
}
    //成员变量
    char *name;
    int age;
    float score;
    //成员函数
    void say(){
        cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
    }
};

一个构造函数和析构函数的创建十分的简单,构造就是类名+()析构就是~+类名+()。但是要注意这边的构造和析构都是没有返回值的,而且这个只是一个简单的应用,我们后面在仔细介绍下这个析构和构造的作用。至少目前来说你可以知道一个就是:构造可以用来初始化变量,析构可以用来释放指针变量

二、 创建类对象

Student liLei;  //创建对象

Student是类名,liLei是对象名。这样我们就通过这个Student类创建了一个对象。除了创建单个对象,还可以创建对象数组。自然也是可以创建一个指针对象,使用指针对象要主要需要使用new开辟空间和delete释放空间。
另外要注意,使用 new 在堆上创建出来的对象是匿名的,没法直接使用,必须要用一个指针指向它,再借助指针来访问它的成员变量或成员函数。栈内存是程序自动管理的,不能使用 delete 删除在栈上创建的对象;堆内存由程序员管理,对象使用完毕后可以通过 delete 删除。

一般有两种创建对象的方式:
一种是在栈上创建,形式和定义普通变量类似;另外一种是在堆上使用 new
关键字创建,必须要用一个指针指向它,读者要记得 delete 掉不再使用的对象。

三、 访问类的成员

创建对象以后,可以使用点号(.)来访问成员变量和成员函数,这和通过结构体变量来访问它的成员类似。当然如果是指针变量,那么就需要使用箭头(->)来访问。类似于下面的方式:

//创建对象
Student stu;
stu.name = "小明";
stu.age = 15;
stu.score = 92.5f;
stu.say();

四、 类成员变量和成员函数

类的成员变量和普通变量一样,也有数据类型和名称,占用固定长度的内存。但是,在定义类的时候不能对成员变量赋值(但是有些编译器支持,不过不介意这么干),因为类只是一种数据类型或者说是一种模板,本身不占用内存空间,而变量的值则需要内存来存储。
类的成员函数也和普通函数一样,都有返回值和参数列表,它与一般函数的区别是:成员函数是一个类的成员,出现在类体中,它的作用范围由类来决定;而普通函数是独立的,作用范围是全局的,或位于某个命名空间内。
前面我们定义一个类的成员函数的时候,是在类的内部实现的,如果说我们在类外应该实现呢?这边就是需要使用到域运算符(::)。

class Student{
public:
    //成员变量
    char *name;
    int age;
    float score;
    //成员函数
    void say();  //函数声明
};
//函数定义
void Student::say(){
    cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
}

在类体中直接定义函数时,不需要在函数名前面加上类名,因为函数属于哪一个类是不言而喻的。但当成员函数定义在类外时,就必须在函数名前面加上类名予以限定。::被称为域解析符(也称作用域运算符或作用域限定符),用来连接类名和函数名,指明当前函数属于哪个类。成员函数必须先在类体中作原型声明,然后在类外定义,也就是说类体的位置应在函数定义之前

五、 C++类的访问权限

C++通过 public、protected、private 三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、受保护的、私有的,被称为成员访问限定符。所谓访问权限,就是你能不能使用该类中的成员。
在理解这三个访问权限前,需要清除一个概念:类内和类外。所谓的类内,就是在定义类的时候,你给他写的属性和方法那边是类内,相同的你在类外实现它的成员函数的时候也算是类内。那么类外就好理解了,除了这两个地方其他的地方都算是类外。下面这个图比较清楚。
类内类外

下面就可以开始介绍public、protected、private

Public: 所谓的public,就是公共的,那么是公共的就意味这不管是类内还是类外都是可以访问的。
Protected:这个是受保护的,既然是被保护的对于类外来说也是不可以访问的。相对的类内自然是可以访问的。
Private:这是私有的,这个自然听得出来类外肯定是不可以访问的,只有类内是可以访问的。那么你是否会觉得protected和private这两个都是只有类内才可以访问,为啥会有两个呢?不要急,这个我们后面说到继承的时候会说,在继承的时候这两个是有区别的。

六、 类的简单封装

private 关键字的作用在于更好地隐藏类的内部实现,该向外暴露的接口(能通过对象访问的成员)都声明为 public,不希望外部知道、或者只在类内部使用的、或者对外部没有影响的成员,都建议声明为 private。当然有人会觉得为什么我不放protected里面,他对于类外也是不可以访问的,这个我们后面在思考,别急哈!

根据C++软件设计规范,实际项目开发中的成员变量以及只在类内部使用的成员函数(只被成员函数调用的成员函数)都建议声明为private,而只将允许通过对象调用的成员函数声明为 public。

有读者可能会提出疑问,将成员变量都声明为 private,如何给它们赋值呢,又如何读取它们的值呢?我们可以额外添加两个 public 属性的成员函数,一个用来设置成员变量的值,一个用来获取成员变量的值。也就是所谓的get和set函数。
给成员变量赋值的函数通常称为 set 函数,它们的名字通常以set开头,后跟成员变量的名字;读取成员变量的值的函数通常称为 get 函数,它们的名字通常以get开头,后跟成员变量的名字。除了 set 函数和 get 函数,在创建对象时还可以调用构造函数来初始化各个成员变量,不过构造函数只能给成员变量赋值一次,以后再修改还得借助 set 函数。
那么由此看来,这个所谓的封装就是尽量隐藏类的内部实现,只向用户提供有用的成员函数

七、 类的构造函数

在C++中,有一种特殊的成员函数,它的名字和类名相同,没有返回值,不需要用户显式调用(用户也不能调用),而是在创建对象时自动执行。这种特殊的成员函数就是构造函数(Constructor)。

class Student{
private:
    char *m_name;
    int m_age;
    float m_score;
public:
    //声明构造函数
    Student(char *name, int age, float score);
    //声明普通成员函数
    void show();
};
//定义构造函数
Student::Student(char *name, int age, float score){
    m_name = name;
    m_age = age;
    m_score = score;
}
//定义普通成员函数
void Student::show(){
    cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}

该例在 Student 类中定义了一个构造函数Student(char *, int, float),它的作用是给三个 private 属性的成员变量赋值。要想调用该构造函数,就得在创建对象的同时传递实参,并且实参由( )包围,和普通的函数调用非常类似。

在栈上创建对象时,实参位于对象名后面,例如Student stu(“小明”, 15,92.5f);在堆上创建对象时,实参位于类名后面,例如new Student(“李华”, 16, 96)。

构造函数必须是 public 属性的,否则创建对象时无法调用。当然,设置为 private、protected 属性也不会报错,但是没有意义,当然也不是说绝对的,有一种模式叫单例模式,可以去了解下,它的构造函数就是一个私有成员函数。
构造函数没有返回值,因为没有变量来接收返回值,即使有也毫无用处,这意味着,不管是声明还是定义,函数名前面都不能出现返回值类型,即使是 void 也不允许;函数体中不能有 return 语句,应该说是一般不出现
有学过一点C++的同学都是知道成员函数是可以重载的,那么我们构造函数是否可以重载呢?答案很显然是可以的。和普通成员函数一样,构造函数是允许重载的。一个类可以有多个重载的构造函数,创建对象时根据传递的实参来判断调用哪一个构造函数。构造函数的调用是强制性的,一旦在类中定义了构造函数,那么创建对象时就一定要调用,不调用是错误的。如果有多个重载的构造函数,那么创建对象时提供的实参必须和其中的一个构造函数匹配;反过来说,创建对象时只有一个构造函数会被调用。

class Student{
private:
    char *m_name;
    int m_age;
    float m_score;
public:
    Student();
Student(char *name, int age, float score);
};
Student::Student(){
    m_name = NULL;
    m_age = 0;
    m_score = 0.0;
}
Student::Student(char *name, int age, float score){
    m_name = name;
    m_age = age;
    m_score = score;
}

对上面示例中的代码,如果写作Student stu或者new Student就是错误的,因为类中包含了构造函数,而创建对象时却没有调用。我们使用下面的两种方式来调用其构造函数。

//调用构造函数 Student(char *, int, float)
Student stu("小明", 15, 92.5f);
//调用构造函数 Student()
Student *pstu = new Student();

一个类必须有构造函数,要么用户自己定义,要么编译器自动生成。一旦用户自己定义了构造函数,不管有几个,也不管形参如何,编译器都不再自动生成。而自动生成的构造函数,这个构造函数的函数体是空的,也没有形参,也不执行任何操作。

八、 C++构造函数初始化列表

构造函数的一项重要功能是对成员变量进行初始化,为了达到这个目的,可以在构造函数的函数体中对成员变量一一赋值,还可以采用初始化列表。
对于一一赋值的方式这边就不在说了,就是使用this指针,将传递进来的参数,赋值给类的私有变量。那么什么是初始化列表呢?是在函数首部与函数体之间添加了一个冒号(:),后面紧跟对应的赋值语句。我们来看下面的一个例子。

class Student{
private:
    char *m_name;
    int m_age;
    float m_score;
public:
    Student(char *name, int age, float score);};
//采用初始化列表
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){
    //TODO:
}

上面的就是初始化列表了,在函数首部与函数体之间添加了一个冒号,后面紧跟m_name(name), m_age(age), m_score(score)语句,这个语句的意思相当于函数体内部的m_name = name; m_age = age; m_score = score;语句,也是赋值的意思。
但是有一个要特别记住,就是初始化const变量的时候,初始化 const 成员变量的唯一方法就是使用初始化列表

九、 析构函数

创建对象时系统会自动调用构造函数进行初始化工作,同样,销毁对象时系统也会自动调用一个函数来进行清理工作,例如释放分配的内存、关闭打开的文件等,这个函数就是析构函数。
析构函数(Destructor)也是一种特殊的成员函数,没有返回值,不需要程序员显式调用(程序员也没法显式调用),而是在销毁对象时自动执行。构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个~符号。

注意:析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。如果用户没有定义,编译器会自动生成一个默认的析构函数。

class Student{
private:
    char *m_name;
    int m_age;
    float m_score;
public:
    Student();
Student(char *name, int age, float score);
//析构函数
~ Student();
};

析构函数执行的时机

  1. 在所有函数之外创建的对象是全局对象,它和全局变量类似,位于内存分区中的全局数据区,程序在结束执行时会调用这些对象的析构函数。
  2. 在函数内部创建的对象是局部对象,它和局部变量类似,位于栈区,函数执行结束时会调用这些对象的析构函数。
  3. new 创建的对象位于堆区,通过 delete 删除时才会调用析构函数;如果没有 delete,析构函数就不会被执行。

十、 this指针

this 是 C++ 中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员。所谓当前对象,是指正在使用的对象。例如对于stu.show();,stu 就是当前对象,this 就指向 stu。this 只能用在类的内部,通过 this 可以访问类的所有成员,包括 private、protected、public 属性的。注意,this 是一个指针,要用->来访问成员变量或成员函数。
很多时候我们都是通过this指针来访问类内部的成员(建议这么使用),同时它也是当我们参数和类内成员重名后的区分。

那么到底什么是this指针呢?
this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给 this。不过 this 这个形参是隐式的,它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。成员函数最终被编译成与对象无关的普通函数,除了成员变量,会丢失所有信息,所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。这个额外的参数,实际上就是 this,它是成员函数和成员变量关联的桥梁

下面就是一个使用this指针的例子。

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<<this->name<<"的年龄是"<<this->age<<",成绩是"<<this->score<<endl;
}

十一、 C++中的静态变量

对象的内存中包含了成员变量,不同的对象占用不同的内存,这使得不同对象的成员变量相互独立,它们的值不受其他对象的影响。例如有两个相同类型的对象 a、b,它们都有一个成员变量 m_name,那么修改 a.m_name 的值不会影响 b.m_name 的值。可是有时候我们希望在多个对象之间共享数据,对象 a 改变了某份数据后对象 b 可以检测到。共享数据的典型使用场景是计数,以前面的 Student 类为例,如果我们想知道班级中共有多少名学生,就可以设置一份共享的变量,每次创建对象时让该变量加 1。
在C++中,我们可以使用静态成员变量来实现多个对象共享数据的目标。静态成员变量是一种特殊的成员变量,它被关键字static修饰,例如:

class Student{
public:
    Student(char *name, int age, float score);
    void show();
public:
    static int m_total;  //静态成员变量
private:
    char *m_name;
    int m_age;
    float m_score;
};

这段代码声明了一个静态成员变量 m_total,用来统计学生的人数。
static 成员变量属于类,不属于某个具体的对象,即使创建多个对象,也只为 m_total 分配一份内存,所有对象使用的都是这份内存中的数据。当某个对象修改了 m_total,也会影响到其他对象。
static 成员变量必须在类声明的外部初始化,具体形式为:type class::name = value; type 是变量的类型,class 是类名,name 是变量名,value 是初始值。

注意:static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。反过来说,没有在类外初始化的
static 成员变量不能使用。

static 成员变量既可以通过对象来访问,也可以通过类来访问。请看下面的例子:

//通过类类访问 static 成员变量
Student::m_total = 10;
//通过对象来访问 static 成员变量
Student stu("小明", 15, 92.5f);
stu.m_total = 20;
//通过对象指针来访问 static 成员变量
Student *pstu = new Student("李华", 16, 96);
pstu -> m_total = 20;

注意:static 成员变量不占用对象的内存,而是在所有对象之外开辟内存,即使不创建对象也可以访问,只要你在类外有进行初始化。具体来说,static 成员变量和普通的 static 变量类似,都在内存分区中的全局数据区分配内存。

使用静态变量注意下面几点:

  1. 一个类中可以有一个或多个静态成员变量,所有的对象都共享这些静态成员变量,都可以引用它。
  2. static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放。这就意味着,static 成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。而普通成员变量在对象创建时分配内存,在对象销毁时释放内存。
  3. 静态成员变量必须初始化,而且只能在类体外进行。
  4. 静态成员变量既可以通过对象名访问,也可以通过类名访问,但要遵循 private、protected 和 public 关键字的访问权限限制。当通过对象名访问时,对于不同的对象,访问的是同一份内存。

十二、 静态函数

在类中,static 除了可以声明静态成员变量,还可以声明静态成员函数。普通成员函数可以访问所有成员(包括成员变量和成员函数),静态成员函数只能访问静态成员。
编译器在编译一个普通成员函数时,会隐式地增加一个形参 this,并把当前对象的地址赋值给 this,所以普通成员函数只能在创建对象后通过对象来调用,因为它需要当前对象的地址。而静态成员函数可以通过类来直接调用,编译器不会为它增加形参 this,它不需要当前对象的地址,所以不管有没有创建对象,都可以调用静态成员函数。普通成员变量占用对象的内存,静态成员函数没有 this 指针,不知道指向哪个对象,无法访问对象的成员变量,也就是说静态成员函数不能访问普通成员变量,只能访问静态成员变量。
静态成员函数与普通成员函数的根本区别在于:普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
和静态成员变量类似,静态成员函数在声明时要加 static,在定义时不能加 static。静态成员函数可以通过类来调用(一般都是这样做),也可以通过对象来调用。
下面是使用例子:

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_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}
//定义静态成员函数
int Student::getTotal(){
    return m_total;
}
float Student::getPoints(){
    return m_points;
}
//使用示例
int total = Student::getTotal();

十三、 const成员函数

const 成员函数可以使用类中的所有成员变量,但是不能修改它们的值,这种措施主要还是为了保护数据而设置的。const 成员函数也称为常成员函数。我们通常将 get 函数设置为常成员函数。读取成员变量的函数的名字通常以get开头,后跟成员变量的名字,所以通常将它们称为 get 函数。常成员函数需要在声明和定义的时候在函数头部的结尾加上 const 关键字,请看下面的例子:

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<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}
//定义常成员函数
char * Student::getname() const{
    return m_name;
}
int Student::getage() const{
    return m_age;
}
float Student::getscore() const{
    return m_score;
}

需要强调的是,必须在成员函数的声明和定义处同时加上 const 关键字。例如:char getname() const和chargetname()是两个不同的函数原型,如果只在一个地方加 const 会导致声明和定义处的函数原型冲突。

最后强调一下,const位置不同,那么对应的含义也不同:

  1. 函数开头的 const 用来修饰函数的返回值,表示返回值是 const 类型,也就是不能被修改,例如const char * getname()。
  2. 函数头部的结尾加上 const 表示常成员函数,这种函数只能读取成员变量的值,而不能修改成员变量的值,例如char * getname() const。
  • 10
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值