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 按照声明的顺序依次排列,和结构体非常类似,也会有内存对齐的问题。
- CPP复习笔记 2
- CPP复习笔记 1
- CPP复习笔记 3
- CPP复习笔记 4
- cpp复习2-类型转换
- cpp复习
- CPP学习笔记-2
- 根据实例复习Cpp
- CPP复习总结
- CPP 复习 记录
- 复习笔记-数据类型2
- 数据结构复习笔记(2)
- scjp复习笔记(2)
- 复习笔记 2
- JDBC 复习笔记2
- CCI 复习笔记 2
- SQL复习笔记2
- TIJ 复习笔记2
- jquery操作select
- vs2013配置opencv2.0
- 网站架构核心要素
- LeetCode: Move Zeroes
- HBase安装
- CPP复习笔记 2
- 自身连接
- servlet与JSP区别
- Opencv(1)---图像读取、显示、保存
- jms简介
- Linux进度条实现与Makefile文件创建
- 2016 Multi-University Training Contest 2 -Keep On Movin
- 当路由器外网IP变更时,执行操作
- 2014年第五届蓝桥杯C/C++ C组决赛真题题解