CPP复习笔记 2

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("Tome");
    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 函数,在创建对象时还可以调用构造函数来初始化各个成员变量,我们将在《C++构造函数》一节中展开讨论。不过构造函数只能给成员变量赋值一次,以后再修改还得借助 set 函数。

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

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

有读者可能会说,额外添加 set 函数和 get 函数多麻烦,直接将成员变量设置为 public 多省事!确实,这样做 99.9% 的情况下都不是一种错误,我也不认为这样做有什么不妥;但是,将成员变量设置为 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 按照声明的顺序依次排列,和结构体非常类似,也会有内存对齐的问题。

时间: 2024-10-13 00:24:17

CPP复习笔记 2的相关文章

CPP复习笔记 4

C++ this指针详解 his 是 C++ 中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员. 所谓当前对象,是指正在使用的对象.例如对于stu.show();,stu 就是当前对象,this 就指向 stu. #include <iostream> using namespace std; class Student{ public: void setname(char *name); void setage(int age); void set

CPP复习笔记 3

--------------- CPP函数编译原理和成员函数的实现 从上节的分析中能够看出.对象的内存中仅仅保留了成员变量,除此之外没有不论什么其它信息,程序运行时不知道 stu 的类型为 Student,也不知道它还有四个成员函数 setname().setage().setscore().show(),C++ 到底是怎样通过对象调用成员函数的呢? C++函数的编译 C++和C语言的编译方式不同.C语言中的函数在编译时名字不变,或者仅仅是简单的加一个下划线_(不同的编译器有不同的实现),比如,

C++复习笔记(一)文件结构

1.文件结构 每个C++/C程序通常分为两个文件: a.用于保存程序的声明(declaration),称为头文件. b.用于保存程序的实现(implementation),称为定义文件(definition). C++/C程序的头文件以".h"为后缀,C程序的定义文件以".c"为后缀,C++程序的定义文件通常以".cpp"为后缀. 头文件的结构: 头文件由三个部分内容组成: (1)头文件开头处的版权和版本声明. (2)预处理块. (3)函数和类结

安卓开发复习笔记——Fragment+FragmentTabHost组件(实现新浪微博底部菜单)

记得之前写过2篇关于底部菜单的实现,由于使用的是过时的TabHost类,虽然一样可以实现我们想要的效果,但作为学习,还是需要来了解下这个新引入类FragmentTabHost 之前2篇文章的链接: 安卓开发复习笔记——TabHost组件(一)(实现底部菜单导航) 安卓开发复习笔记——TabHost组件(二)(实现底部菜单导航) 关于Fragment类在之前的安卓开发复习笔记——Fragment+ViewPager组件(高仿微信界面)也介绍过,这里就不再重复阐述了. 国际惯例,先来张效果图: 下面

计算机图形学 复习笔记

计算机图形学 复习笔记 (个人整理,仅做复习用 :D,转载注明出处:http://blog.csdn.net/hcbbt/article/details/42779341) 第一章 计算机图形学综述 研究内容 图形的概念:计算机图形学的研究对象 能在人的视觉系统中产生视觉印象的客观对象 包括自然景物.拍摄到的图片.用数学方法描述的图形等等 图形的要素 几何要素:刻画对象的轮廓.形状等 非几何要素:刻画对象的颜色.材质等 图形表示法 点阵表示 枚举出图形中所有的点,简称为图像. 参数表示 由图形的

安卓开发复习笔记——Fragment+ViewPager组件(高仿微信界面)

什么是ViewPager? 关于ViewPager的介绍和使用,在之前我写过一篇相关的文章<安卓开发复习笔记——ViewPager组件(仿微信引导界面)>,不清楚的朋友可以看看,这里就不再重复. 什么是Fragment? Fragment是Android3.0后新增的概念,Fragment名为碎片,不过却和Activity十分相似,具有自己的生命周期,它是用来描述一些行为或一部分用户界面在一个Activity中,我们可以合并多个Fragment在一个单独的activity中建立多个UI面板,或

安卓开发复习笔记——WebView组件

我们专业方向本是JAVA Web,这学期突然来了个手机App开发的课设,对于安卓这块,之前自学过一段时间,有些东西太久没用已经淡忘了 准备随笔记录些复习笔记,也当做温故知新吧~ 1.什么是WebView? WebView(网络视图)能加载显示网页,可以将其视为一个浏览器,它使用了WebKit渲染引擎加载显示网页. 废话不多说,直接上代码 1.需要在xml布局文件中声明WebView组件 1 <WebView 2 android:id="@+id/webview" 3 androi

[Java基础] Java线程复习笔记

先说说线程和进程,现代操作系统几乎无一例外地采用进程的概念,进程之间基本上可以认为是相互独立的,共享的资源非常少.线程可以认为是轻量级的进 程,充分地利用线程可以使得同一个进程中执行多种任务.Java是第一个在语言层面就支持线程操作的主流编程语言.和进程类似,线程也是各自独立的,有自 己的栈,自己的局部变量,自己的程序执行并行路径,但线程的独立性又没有进程那么强,它们共享内存,文件资源,以及其他进程层面的状态等.同一个进程内的 多个线程共享同样的内存空间,这也就意味着这些线程可以访问同样的变量和

oracle从入门到精通复习笔记续集之PL/SQL(轻量版)

复习内容: PL/SQL的基本语法.记录类型.流程控制.游标的使用. 异常处理机制.存储函数/存储过程.触发器. 为方便大家跟着我的笔记练习,为此提供数据库表文件给大家下载:点我下载 为了要有输出的结果,在写PL/SQL程序前都在先运行这一句:set serveroutput on结构:declare--声明变量.类型.游标begin--程序的执行部分(类似于java里的main()方法)exception--针对begin块中出现的异常,提供处理的机制--when...then...--whe