CPP复习笔记 2

来源:互联网 发布:前端如何优化代码 编辑:程序博客网 时间:2024/05/22 06:28

CPP类和对象

——————————————-

CPP类的定义和对象的创建

类是创建对象的模板,一个类可以创建多个对象,每个对象都是类类型的一个变量;创建对象的过程也叫类的实例化。每个对象都是类的一个具体实例(Instance),拥有类的成员变量和成员函数。

类的定义

类是用户自定义的类型,如果程序中要用到类,必须提前说明,或者使用已存在的类(别人写好的类、标准库中的类等),C++语法本身并不提供现成的类的名称、结构和内容。

#include <iostream>using namespace std;class Student{public:    char *name;    int age;    double score;    void say() {        cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;    }};int main(void) {    class Student stu;    stu.name = "小明";    stu.age = 18;    stu.score = 99.4;    stu.say();    return 0;}

class是 C++ 中新增的关键字,专门用来定义类。Student是类的名称;类名的首字母一般大写,以和其他的标识符区分开。{ }内部是类所包含的成员变量和成员函数,它们统称为类的成员(Member);由{ }包围起来的部分有时也称为类体,和函数体的概念类似。public也是 C++ 的新增关键字,它只能用在类的定义中,表示类的成员变量或成员函数具有“公开”的访问权限。

类只是一个模板(Template),编译后不占用内存空间,所以在定义类时不能对成员变量进行初始化,因为没有地方存储数据。只有在创建对象以后才会给成员变量分配内存,这个时候就可以赋值了。

创建对象
Student stu;
在创建对象时,class 关键字可要可不要,但是出于习惯我们通常会省略掉 class 关键字

访问类的成员
stu.name = "hello"
stu.say();
创建对象以后,可以使用点号.来访问成员变量和成员函数,这和通过结构体变量来访问它的成员类似。

stu 是一个对象,占用内存空间,可以对它的成员变量赋值,也可以读取它的成员变量。类通常定义在函数外面,当然也可以定义在函数内部,不过很少这样使用。

使用对象指针
上面代码中创建的对象 stu 在栈上分配内存,需要使用&获取它的地址,例如:

Student stu;Student *pStu = &stu;

pStu 是一个指针,它指向 Student 类型的数据,也就是通过 Student 创建出来的对象。

当然,你也可以在堆上创建对象,这个时候就需要使用前面讲到的new关键字,例如:
Student *pStu = new Student;
在栈上创建出来的对象都有一个名字,比如 stu,使用指针指向它不是必须的。但是通过 new 创建出来的对象就不一样了,它在堆上分配内存,没有名字,只能得到一个指向它的指针,所以必须使用一个指针变量来接收这个指针,否则以后再也无法找到这个对象了,更没有办法使用它。也就是说,使用 new 在堆上创建出来的对象是匿名的,没法直接使用,必须要用一个指针指向它,再借助指针来访问它的成员变量或成员函数。

有了对象指针后,可以通过箭头->来访问对象的成员变量和成员函数,这和通过结构体指针来访问它的成员类似,请看下面的示例:

pStu -> name = "小明";pStu -> age = 15;pStu -> score = 92.5f;pStu -> say();

本次实例

#include <iostream>using namespace std;class Student {public:    char *name;    int age;    double score;    void say() {        cout<<name<<"'s age is "<<age<<", and score is "<<score<<endl;    }};int main (void) {    Student *pStu = new Student;    pStu->name = "wangjun";    pStu->age = 16;    pStu->score = 99.0;    pStu->say();    return 0;}

总结

本节重点讲解了两种创建对象的方式:

  • 在栈上创建,形式和定义普通变量类似;
  • 在堆上创建,必须要用一个指针指向它,读者要记得 delete 掉不再使用的对象。

通过对象名字访问成员使用点号.,通过对象指针访问成员使用箭头->,这和结构体非常类似。

——————————————-

CPP类的成员变量和成员函数

类可以看做是一种数据类型,它类似于普通的数据类型,但又有别于普通的数据类型,类这种数据类型是包含成员变量和成员函数的集合

类的成员变量和普通变量一样,也有数据类型和名称,占用固定长度的内存。但是在定义类的时候不能对成员变量赋值,因为类只是一种数据类型,或者说是一种模板,本身不占用内存空间,而变量的值则需要内存来存储。

类的成员函数也和普通函数一样,都有返回值和参数列表,它与一般函数的区别是:成员函数是一个类的成员,出现在类体中,它的作用范围由类来决定;而普通函数是独立的,作用范围是全局的,或位于某个命名空间内。

#include <iostream>#include <iomanip>using namespace std;class Student {public:    char *name;    int age;    double score;    void say() {        cout<<name<<" 的年龄是 "<<age<<", 成绩是 "<<setprecision(8)<<score<<endl;    }};int main (void) {    Student *pStu = new Student;    pStu->name = "wangjun";    pStu->age = 16;    pStu->score = 99.5;    pStu->say();    return 0;}

这段代码在类体中定义了成员函数。你也可以只在类体中声明函数,而将函数定义放在类体外面,如下:

#include <iostream>#include <iomanip>using namespace std;class Student {public:    char *name;    int age;    double score;    void say(); };void Student::say() {    cout<<name<<" 的年龄是 "<<age<<",成绩是 "<<score<<endl;}int main (void) {    Student *pStu = new Student;    pStu->name = "wangjun";    pStu->age = 16;    pStu->score = 99.5;    pStu->say();    return 0;}

在类体中直接定义函数时,不需要在函数名前面加上类名,因为函数属于哪一个类是不言而喻的。

当成员函数定义在类外时,就必须在函数名前面加上类名予以限定。::被称为域解析符(也称作用域运算符或作用域限定符),用来连接类名和函数名,指明当前函数属于哪个类。

成员函数必须先在类体中作原型声明,然后在类外定义,也就是说类体的位置应在函数定义之前。

inline成员函数

在类体中和类体外定义成员函数是有区别的:在类体中定义的成员函数会自动成为内联函数,在类体外定义的不会。当然,在类体内部定义的函数也可以加 inline 关键字,但这是多余的,因为类体内部定义的函数默认就是内联函数。

内联函数一般不是我们所期望的,它会将函数调用处用函数体替代,所以我建议在类体内部对成员函数作声明,而在类体外部进行定义,这是一种良好的编程习惯,实际开发中大家也是这样做的。

当然,如果你的函数比较短小,希望定义为内联函数,那也没有什么不妥的。

如果你既希望将函数定义在类体外部,又希望它是内联函数,那么可以在定义函数时加 inline 关键字。当然你也可以在函数声明处加 inline,不过这样做没有效果,编译器会忽略函数声明处的 inline。

——————————————-

CPP类成员的访问权限

C++通过 public、protected、private 三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、受保护的、私有的,被称为成员访问限定符。所谓访问权限,就是你能不能使用该类中的成员。

理解1:在类的内部(定义类的代码内部),无论成员被声明为 public、protected 还是 private,都是可以互相访问的,没有访问权限的限制。类的声明和成员函数的定义都是类定义的一部分

理解2:在类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问 public 属性的成员,不能访问 private、protected 属性的成员。

下面通过一个 Student 类来演示成员的访问权限:

#include <iostream>using namespace std;class Student {private:    char *mName;    int mAge;    double mScore;public:    void setname(char *name);    void setage(int age);    void setscore(double score);    void show();};void Student::setname(char *name) {    mName = name;}void Student::setage(int age) {    mAge = age;}void Student::setscore(double score) {    mScore = score;}void Student::show() {    cout<<mName<<"'s age is "<<mAge<<", score is "<<mScore<<endl;}int main(void) {    Student stu1;    stu1.setname("Jim");    stu1.setage(19);    stu1.setscore(99.1);    stu1.show();    Student *stu2 = new Student;    stu2->setname("Tom");    stu2->setage(22);    stu2->setscore(98.7);    stu2->show();    return 0;}

类的声明和成员函数的定义都是类定义的一部分,在实际开发中,我们通常将类的声明放在头文件中,而将成员函数的定义放在源文件中。

#include <iostream>using namespace std;class Student {private:    char *m_name;    int m_age;    double m_score;public:    void setname(char *name);    void setage(int age);    void setscore(double score);    void show();};void Student::setname(char *name) {    m_name = name;}void Student::setage(int age) {    m_age = age;}void Student::setscore(double score) {    m_score = score;}void Student::show() {    cout<<m_name<<"'s age is "<<m_age<<", score is "<<m_score<<endl;}int main(void) {    Student stu1;    stu1.setname("Jim");    stu1.setage(14);    stu1.setscore(98.7);    stu1.show();    Student *stu2 = new Student;    stu2->setname("Tom");    stu2->setage(19);    stu2->setscore(99.5);    stu2->show();    return 0;}

类中的成员变量 m_name、m_age 和m_ score 被设置成 private 属性,在类的外部不能通过对象访问。也就是说,私有成员变量和成员函数只能在类内部使用,在类外都是无效的。

成员函数 setname()、setage() 和 setscore() 被设置为 public 属性,是公有的,可以通过对象访问。

private 后面的成员都是私有的,直到有 public 出现才会变成共有的;public 之后再无其他限定符,所以 public 后面的成员都是共有的。

成员变量大都以m_开头,这是约定成俗的写法,不是语法规定的内容。以m_开头既可以一眼看出这是成员变量,又可以和成员函数中的形参名字区分开。

以 setname() 为例,如果将成员变量m_name的名字修改为name,那么 setname() 的形参就不能再叫name了,得换成诸如name1、_name这样没有明显含义的名字,否则name=name;这样的语句就是给形参name赋值,而不是给成员变量name赋值。

因为三个成员变量都是私有的,不能通过对象直接访问,所以必须借助三个 public 属性的成员函数来修改它们的值。下面的代码是错误的:

Student stu;//m_name、m_age、m_score 是私有成员变量,不能在类外部通过对象访问stu.m_name = "小明";stu.m_age = 15;stu.m_score = 92.5f;stu.show();

简单地谈类的封装

private 关键字的作用在于更好地隐藏类的内部实现,将向外暴露的接口(能通过对象访问的成员)都声明为 public,不希望外部知道、或者只在类内部使用的、或者对外部没有影响的成员,都建议声明为 private。

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

另外还有一个关键字 protected,声明为 protected 的成员在类外也不能通过对象访问,但是在它的派生类内部可以访问,这点我们将在后续章节中介绍,现在你只需要知道 protected 属性的成员在类外无法访问即可。
有读者可能会提出疑问,将成员变量都声明为 private,如何给它们赋值呢,又如何读取它们的值呢?

我们可以额外添加两个 public 属性的成员函数,一个用来设置成员变量的值,一个用来修改成员变量的值。上面的代码中,setname()、setage()、setscore() 函数就用来设置成员变量的值;如果希望获取成员变量的值,可以再添加三个函数 getname()、getage()、getscore()。

给成员变量赋值的函数通常称为 set 函数,它的名字通常以set开头,后跟成员变量的名字;读取成员变量的值的函数通常称为 get 函数,它的名字通常以get开头,后跟成员变量的名字。

除了 set 函数和 get 函数,在创建对象时还可以调用构造函数来初始化各个成员变量,我们将在学习构造函数时展开讨论。不过构造函数只能给成员变量赋值一次,以后再修改还得借助 set 函数。

这种将成员变量声明为 private、将部分成员函数声明为 public 的做法体现了类的封装性

所谓封装,是指尽量隐藏类的内部实现,只向用户提供有用的成员函数。

对private和public的更多说明

声明为 private 的成员和声明为 public 的成员的次序任意,既可以先出现 private 部分,也可以先出现 public 部分。如果既不写 private 也不写 public,就默认为 private。

在一个类体中,private 和 public 可以分别出现多次。每个部分的有效范围到出现另一个访问限定符或类体结束时(最后一个右花括号)为止。但是为了使程序清晰,应该养成这样的习惯,使每一种成员访问限定符在类定义体中只出现一次。

——————————————-

C++类对象的内存模型

类是创建对象的模板,不占用内存空间,不存在于编译后的可执行文件中;而对象是实实在在的数据,需要内存来存储。对象被创建时会在栈区或者堆区分配内存。

直观的认识是,如果创建了 10 个对象,就要分别为这 10 个对象的成员变量和成员函数分配内存,如下图所示:

这里写图片描述

不同对象的成员变量的值可能不同,需要单独分配内存来存储。但是不同对象的成员函数的代码是一样的,上面的内存模型保存了 10 分相同的代码片段,浪费了不少空间,可以将这些代码片段压缩成一份。

事实上编译器也是这样做的,编译器会将成员变量和成员函数分开存储:分别为每个对象的成员变量分配内存,但是所有对象都共享同一段函数代码。如下图所示:

这里写图片描述

成员变量在堆区或栈区分配内存,成员函数在代码区分配内存。


32位系统,vc编译器中,
short占 2 字节,
int 、float、long 都占 4 字节,
只有double 占8 字节
(容易弄错的就是 short 和 long)


#include <iostream> //C++标准输入输出库using namespace std; //C++标准命名空间class Student { //类的定义private:        //私有成员类型    char *m_name; //成员名用m_  以和之后函数形参相区别    int m_age;    double m_score;public:         //公有成员类型    void setname(char *name); //成员函数  返回值 函数名 形参列表    void setage(int age);    void setscore(double score);    void show();};void Student::setname(char *name) { //类外对函数的定义——要用::域解析符表明函数属于哪个类    m_name = name;                  //成员函数的定义也属于类定义的一部分,所以可以使用类成员变量, }                                   // 利用成员函数中的形参对成员变量进行赋值,再把函数作为接口提供给外界使用void Student::setage(int age) {    m_age = age;}void Student::setscore(double score) {    m_score = score;}void Student::show() {    cout<<m_name<<"'s age is "<<m_age<<", score is "<<m_score<<endl;}int main(void) {    Student stu1;       //在栈上创建对象,操作类成员变量和成员函数用‘.’符号    stu1.setname("Jim");    stu1.setage(14);    stu1.setscore(98.7);    stu1.show();    Student *stu2 = new Student; //在堆上创建对象,new 与 delete 成对出现    stu2->setname("Tom");        //操作类成员用->符号    stu2->setage(19);    stu2->setscore(99.5);    stu2->show();    cout<<sizeof(stu1)<<endl; //类可以看做是一种复杂的数据类型,也可以用sizeof求得该类型的大小。    cout<<sizeof(stu2)<<endl;    cout<<sizeof(*stu2)<<endl;    cout<<sizeof(Student)<<endl;    delete stu2;    return 0;}

Student 类包含三个成员变量,它们的类型分别是 char *、int、float,都占用 4 个字节的内存,加起来共占用 12 个字节的内存。通过 sizeof 求得的结果等于 12,恰好说明对象所占用的内存仅仅包含了成员变量。

类可以看做是一种复杂的数据类型,也可以使用 sizeof 求得该类型的大小。从运行结果可以看出,在计算类这种类型的大小时,只计算了成员变量的大小,并没有把成员函数也包含在内。

对象的大小只受成员变量的影响,和成员函数没有关系。

假设 stu 的起始地址为 0X1000,那么该对象的内存分布如下图所示:

这里写图片描述

m_name、m_age、m_score 按照声明的顺序依次排列,和结构体非常类似,也会有内存对齐的问题。

0 0
原创粉丝点击