假设我们要设计一个包含以下操作的 Sales_data 类:

1.一个 isbn 成员函数,用于返回对象的 book_no 成员变量

2.一个 combine 成员函数,用于将一个 Sales_data 对象加到另一个 Sales_data 对象上

3.一个名为 add 的函数,执行两个 Sales_data 对象的加法

4.一个 read 函数,将数据从 istream 都入到 Sales_data 对象中

5.一个 print 函数,将 Sales_data 对象的值输出到 ostream

 struct Sales_data{
//数据成员
std::string book_no;
unsigned units_sold = ;
double revenue = 0.0; //函数成员
std::string isbn() const {
return book_no;
// return this->book_no;//等价语句
}
Sales_data& combine(const Sales_data&);
double avg_price() const;
};
//Sales_data的非成员函数声明
Sales_data add(const Sales_data&, const Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);

注意到:

类的成员函数的声明必须在类的内部,它的定义则既可以在类的内部,也可以在内的外部。
作为接口组成部分的非成员函数,它的定义和声明都在类的外部。
定义在类内部的函数都是隐式的 inline 函数。

引入 this:

在上列中,isbn 函数中只有一条 return 语句,用于返回 Sales_data 对象的 book_no 数据成员。关于 isbn 函数一件有意思的事情是:它是如何获得 bool_no 成员所依赖的对象呢?

Sales_data total;//创建一个Sales_data类的实例

total.isbn();//访问 total 对象的 isbn 成员函数

可以发现,当我们调用某个类成员函数的时候,实际上是在替某个对象调用它。如果 isbn 指向 Sales_data 的成员,则它隐式的指向调用该函数的对象的成员。如上列所示的调用中,当 isbn 返回 book_no 时,实际上它隐式的返回 total.book_no。

成员函数通过一个名为 this 的额外的隐式参数来访问调用它的那个对象。this 是一个指向对象本身的常量指针。所以 return book_no;等价于 return this->book_no;其效果是 return total.bool_no。

引入 const 成员函数:

isbn 函数的另一个关键之处是紧随参数列表之后的 const 关键字,这里,const 的作用是修改隐式 this 指针的类型。默认情况下 this 的类型是指向类类型非常量版本的常量指针(具有顶层 const 但不具有底层 const)。例如在 Sales_data 成员函数中,this 的类型是 Sales_data *const。尽管 this 是隐式的,但它仍然需要遵循初始化规则,意味着在默认情况下我们不能把 this 绑定到一个常量对象上。这一情况使得我们不能在一个常量对象上调用普通的成员函数:

 #include <iostream>
using namespace std; struct Sales_data{
//数据成员
std::string book_no;
//函数成员
std::string isbn() {
return book_no;
// return this->book_no;//等价语句
}
}; int main(void){
Sales_data total;
const Sales_data gel;
total.isbn();
// gel.isbn();//错误: isbn 成员函数没有将隐式的 this 指针声类型修改成具有底层 const,所以 isbn 成员函数不能被常量对象调用
return ;
}

常量对象,以及常量对象的引用或指针都只能调用常量成员函数。

类作用域和成员函数:

类本身就是一个作用域。类的成员函数的定义嵌套在类的作用域之内,因此,isbn 中用到的名字 book_no 其实就是定义在 Sales_data 内的数据成员。值得注意的是,即使 book_no 定义在 sibn 之后,isbn 也还是能够使用 book_no。编译器分两部处理类:首先编译成员的声明,然后才轮到成员函数体(如果有的话)。因此,成员函数体可以随意使用类中的其他成员而无需在意这些成员出现的次序:

 struct Sales_data{
//函数成员
std::string isbn() {
return book_no;
// return this->book_no;//等价语句
}
//数据成员
std::string book_no;
};

需要特别注意的是,由于编译器要处理完类中的全部声明后才会处理成员函数的定义,成员函数中使用的名字我们可以不用在意它声明在成员函数定义之前还是之后。但是声明中使用的名字(定义的类型名等),包括返回类型或者参数列表中使用的名字,都必须在使用前可见。如果某个成员的声明使用了类中尚未出现的名字,则编译器将会在定义该类的作用域中继续查找:

 #include <iostream>
using namespace std; using small_int = string; class ac26{
public:
void lou(small_int cnt){//这里的small_int等价于string而非int
gel += ;
} private:
int gel = ;
using small_int = int;
}; int main(void){
ac26 x;
x.lou();
return ;
}

当编译器看到 lou 函数的声明语句时,它先会在类 ac26 中 lou 声明之前的范围内查找 small_int 的声明。因为没有找到匹配的成员,所以编译器会接着到 ac26 的外层作用域中查找。在本例中,编译器会找到 using small_int = string;语句,所以 lou 的形参 cnt 是 string 类型的。在 main 函数中 x.lou(3) 调用会 error。

还有一点需要注意的是:对于成员函数内部使用的名字,如果在类内没有找到声明,编译器会接着在外层作用域接着查找(这一点是和用于类成员声明的名字一样的):

 #include <iostream>
using namespace std; string book_no = "world"; class Sales_data1{
public:
std::string isbn() {
return book_no;//返回的是Sales_data1类内定义的book_no变量
} private:
std::string book_no = "hello";
}; class Sales_data2{
public:
std::string isbn() {
return book_no;//返回Sales_data2类外定义的book_no
} // private:
// std::string book_no = "hello";
}; int main(void){
Sales_data1 x1;
Sales_data2 x2;
cout << x1.isbn() << endl;//输出hello
cout << x2.isbn() << endl;//输出world
return ;
}

在类外部定义成员函数:

像其他函数一样,当我们在类的外面定义成员函数时,成员函数的定义必须与它的声明匹配。也就是说,返回类型,参数列表和函数名都得与类内部的声明保持一致。如果成员被声明成常量成员函数,那么它的定义也必须在参数列表后明确指定 const 属性。同时,类外部定义的成员的名字必须包含它所属的类名:

 #include <iostream>
using namespace std; struct Sales_data{
//数据成员
unsigned units_sold = ;
double revenue = 0.0; //成员函数声明
double avg_price() const;
}; double Sales_data::avg_price() const{
if(units_sold) return revenue / units_sold;//定义在类外部的成员函数也能直接使用类成员
return ;
} int main(void){
Sales_data total;
cout << total.avg_price() << endl;
return ;
}

定义一个返回 this 对象的函数:

在一开始的那个 Sales_data 类的设计代码中,函数 combine 的设计初衷类似于复合赋值运算符 +=,调用该函数的对象代表的时赋值运算符左侧的运算对象,右侧运算对象则通过显示的实参被传入函数:

 #include <iostream>
using namespace std; struct Sales_data{
//数据成员
std::string book_no;
unsigned units_sold = ;
double revenue = 0.0; //成员函数声明
Sales_data& combine(const Sales_data&);
}; Sales_data& Sales_data::combine(const Sales_data &rhs){
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;//解引用获得调用该函数的对象本身
} int main(void){
Sales_data total, x;
total.combine(x);//计算 total + x 并将结果保持到 total
//total 的地址被绑定到隐式的 this 参数上,而引用 rhs 绑定到了 x 上
return ;
}

其中,return 语句解引用 this 以获得执行该函数的对象,即上面的这个调用返回 total 的引用。

定义类相关的非成员函数:

像前面的 add, read, print 函数,尽管这些函数定义的操作从概念上来说属于类的接口组成部分,但它们实际上不属于类本身。我们应该将其定义成非成员函数。定义非成员函数和其他函数一样,通常把函数的声明和定义分离开来。如果函数概念上属于类但是不定义在类中,则它一般应与类声明在同一个头文件内。在这种方式下,用户使用接口的任何部分都只需要引入一个头文件。

定义 read 和 print 函数:

 istream &read(istream &is, Sales_data &item){//从给定流中将数据读到给定的对象里
double price = ;
is >> item.book_no >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
} ostream &print(ostream &os, const Sales_data &item){//将给定对象的内容打印到给定的流中
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
//print 函数不负责换行。一般来说执行输出的函数应该尽量减少对格式的控制,这样可以确保由用户代码来决定是否换行
return os;
}

需要注意的是:read 和 print 分别接受各自 IO 类型的引用作为其参数,因为 IO 类属于不能被拷贝的类型,因此我们只能通过引用来传递它们。

定义 add 函数:

 Sales_data add(const Sales_data &lhs, const Sales_data &rhs){
Sales_data sum = lhs;//默认情况下拷贝的是数据成员
sum.combine(rhs);//把 rhs 的数据成员加到 sum 中
return sum;//返回 sum 的副本
}

至此,我们一开始设计的类 Sales_data 就算是完善啦:

 #include <iostream>
using namespace std; struct Sales_data{
//数据成员
std::string book_no;
unsigned units_sold = ;
double revenue = 0.0; //函数成员
std::string isbn() const {
return book_no;
// return this->book_no;//等价语句
}
Sales_data& combine(const Sales_data&);
double avg_price() const;
};
//Sales_data的非成员函数声明
Sales_data add(const Sales_data&, const Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&); double Sales_data::avg_price() const{
if(units_sold) return revenue / units_sold;
return ;
} Sales_data& Sales_data::combine(const Sales_data &rhs){
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
} istream &read(istream &is, Sales_data &item){//从给定流中将数据读到给定的对象里
double price = ;
is >> item.book_no >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
} ostream &print(ostream &os, const Sales_data &item){//将给定对象的内容打印到给定的流中
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
//print 函数不负责换行。一般来说执行输出的函数应该尽量减少对格式的控制,这样可以确保由用户代码来决定是否换行
return os;
} Sales_data add(const Sales_data &lhs, const Sales_data &rhs){
Sales_data sum = lhs;//默认情况下拷贝的是数据成员
sum.combine(rhs);//把 rhs 的数据成员加到 sum 中
return sum;//返回 sum 的副本
} int main(void){
Sales_data total, x;
read(cin, total);
while(read(cin, x)){
total = add(total, x);
print(cout, total);
cout << endl;
}
return ;
}

注意:我们这里定义类使用的 struct 而没有用 class 关键字。实际上 struct 和 class 仅仅只是形式上有所不同而已,我们可以用这两个关键字中的任何一个定义类。唯一一点区别是 struct 和 class 的默认访问权限不太一样。(c++ primer 第五版 240 页)

类1(this指针/const成员函数/类作用域/外部成员函数/返回this对象的函数)的更多相关文章

  1. Python进阶(三)----函数名,作用域,名称空间,f-string,可迭代对象,迭代器

    Python进阶(三)----函数名,作用域,名称空间,f-string,可迭代对象,迭代器 一丶关键字:global,nonlocal global 声明全局变量: ​ 1. 可以在局部作用域声明一 ...

  2. C++ //拷贝构造函数调用时机//1.使用一个已经创建完毕的对象来初始化一个新对象 //2.值传递的方式给函数参数传值 //3.值方式返回局部对象

    1 //拷贝构造函数调用时机 2 3 4 #include <iostream> 5 using namespace std; 6 7 //1.使用一个已经创建完毕的对象来初始化一个新对象 ...

  3. (转)函数中使用 ajax 异步 同步 返回值错误 主函数显示返回值总是undefined -- ajax使用总结

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAloAAAE0CAIAAAB7LwoKAAAgAElEQVR4nO2dy6sc152A6+/R2mXwSn ...

  4. C++类的this指针详解

    这篇文章主要讲解隐式this指针的概念,以及如何使用,包含const 先直接给出一个C++Primer里的类,你可能还不能完全看懂,但是不着急,我们一点点解释 class Sales_data { s ...

  5. Python10/22--面向对象编程/类与对象/init函数

    类: 语法: class关键字 类名# 类名规范 大写开头 驼峰命名法class SHOldboyStudent: # 描述该类对象的特征 school = "上海Oldboy" ...

  6. js总结(二):函数、作用域和this

    function Container( properties ) { var objthis = this; for ( var i in properties ) { (function(){ // ...

  7. (二)JavaScript之[函数]与[作用域]

    3].函数 /** * 事件驱动函数. * 函数执行可重复使用的代码 * * 1.带参的函数 * 2.带返回值的函数 * 3.局部变量 * * 4.全局变量 * 在函数外的:不用var声明,未声明直接 ...

  8. JavaScript对象,函数,作用域

    JavaScript对象 在 JavaScript中,几乎所有的事物都是对象.JavaScript 对象是拥有属性和方法的数据. var car = {type:"Fiat", m ...

  9. 类 this指针 const成员函数

    C++ Primer 第07章 类 7.1.2 ​Sales_data类的定义如下: #ifndef SALES_DATA_H #define SALES_DATA_H #include <st ...

随机推荐

  1. Python多线程-守护线程

    守护线程:守护着非守护线程,守护线程和非守护线程同时运行,当非守护线程运行结束后,无论守护线程有没有运行完,脚本都会停止运行 首先看一段普通的多线程实例 # -*- coding:utf-8 -*- ...

  2. PHP框架 Laravel

    PHP框架 CI(CodeIgniter) http://www.codeigniter.com/ http://codeigniter.org.cn/ Laravel PHP Laravel htt ...

  3. c# tcp udp 的使用场景

    之前用tcp实现了一个案例(远程协助),后来我考虑用udp去实现它,于是又研究了下udp,我发现理论上udp可以做到,但是有一些问题不知道会不会有瓶颈 我参照网上写了一个简单的示例如下 服务端接收.发 ...

  4. beijing

    #include<stdio.h> #include<string.h> #include<stdlib.h> #include<graphics.h> ...

  5. 201671010140. 2016-2017-2 《Java程序设计》java学习第六章

    java学习第六章    本周对与java中的接口,lambda表达式与内部类进行了学习,以下是我在学习中的一些体会:    1.接口: <1>.接口中的所有常量必须是public sta ...

  6. Solr查询过程源码分析

    原文出自:http://blog.csdn.net/flyingpig4/article/details/6305488 <pre name="code" class=&qu ...

  7. Java 计算两个日期相差的天数

    import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; impor ...

  8. selenium2 断言失败自动截图 (四)

    一般web应用程序出错过后,会抛出异常.这个时候能截个图下来,当然是极好的. selenium自带了截图功能. //获取截图file File scrFile= ((TakesScreenshot)d ...

  9. Extend volumn in ubuntu 14.04

    运行环境: ubuntu 14.04, VMware12.5.7 1. VMware上点击 虚拟机->设置->硬盘(SCSI)->扩展选项,设置自己希望的ubuntu磁盘运行空间大小 ...

  10. linux环境配置与使用合集

    配置linux和samba共享 1. 安装linux操作系统 2. 通过windows操作系统ping linux看看是否可以ping通 3. 相关软件安装 a. 安装samba sudo apt-g ...