假设我们要设计一个包含以下操作的 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. 讲解一下this (作用域)

    this的指向:普通函数内的this指向全局变量 构造函数内部this指向新创建出来的对象 对象方法内的this指向的是调用该方法的对象 call,apply,bind可以改变this的指向

  2. spring-cloud配置高可用eureka服务端

    spring-cloud配置eureka服务端 eureka用来发现其他程序 依赖 <?xml version="1.0" encoding="UTF-8" ...

  3. Python Twisted系列教程4:由Twisted支持的诗歌客户端

    作者:dave@http://krondo.com/twisted-poetry/  译者:杨晓伟(采用意译) 你可以在这里从头开始阅读这个系列. 第一个twisted支持的诗歌服务器 尽管Twist ...

  4. windows网络服务之配置网络负载均衡(NLB)群集

    O首页51CTO博客我的博客 搜索 每日博报 社区:学院论坛博客下载更多            登录注册 家园 学院 博客 论坛 下载 自测 门诊 周刊 读书 技术圈 曾垂鑫的技术专栏 http:// ...

  5. leetcode481

    public class Solution { public int MagicalString(int n) { ) ; ) ; ]; a[] = ; a[] = ; a[] = ; , tail ...

  6. jenkins容器权限被拒绝

    问题,我们从官网上面pull下jenkins后,如果直接运行容器的没问题 docker run -d -p 8080:8080 -v jenkins:latest 不过我们可能需要映射下容器内部的地址 ...

  7. Class.forName和ClassLoader.loadClass区别(转)

    Java中class是如何加载到JVM中的:1.class加载到JVM中有三个步骤    装载:(loading)找到class对应的字节码文件.    连接:(linking)将对应的字节码文件读入 ...

  8. Linux 搭建NFS文件服务器实现文件共享

    我们接着玩Linux,O(∩_∩)O哈哈~ 1.什么是nfs NFS(Network File System)即网络文件系统,是FreeBSD支持的文件系统中的一种,它允许网络中的计算机之间通过TCP ...

  9. CSS变量教程

    今年三月,微软宣布 Edge 浏览器将支持 CSS 变量. 这个重要的 CSS 新功能,所有主要浏览器已经都支持了.本文全面介绍如何使用它,你会发现原生 CSS 从此变得异常强大. 一.变量的声明 声 ...

  10. linux删除文件后磁盘空间未释放的问题

    很可能是该文件还被其它进程使用. 使用: lsof | grep deleted | grep $FILE_NAME 可以看到正使用该文件的进程,将之kill即可.也可以查看进程来验证: ls -l ...