C++ 类的知识 | 构造函数再探、匿名对象、友元函数、内部类、类的const成员、类的static成员

文章目录

  • 构造函数再探
    • 以下代码共调用多少次拷贝构造函数
    • 委托构造函数
      • 概念
      • 形式
  • 匿名对象
  • 友元
    • 友元的声明
    • 友元类
    • 令成员函数作为友元
    • 函数重载和友元
    • 注意
  • 内部类
    • 特性
  • 类的const成员
    • 可变数据成员
  • 类的static成员
    • 概念
    • 关于static
    • 静态成员的类内初始化
    • 静态成员能用于某些普通成员不能的场景


构造函数再探

以下代码共调用多少次拷贝构造函数

Widget f(Widget u)
{  Widget v(u);Widget w=v;return w;
}int main(){Widget x;Widget y=f(f(x));}

这道题我第一眼看,在调用f的时候,会用实参来构造对象u,在用对象u构建v,然后用v构建w,最后因为是传值返回,会用w再构造一个临时量,所以调用一次f会使用4次拷贝构造函数,最后再用这两次f的返回值来构造y,应该是9次。

但是答案却是7次,让我十分不解,所以我去论坛搜索了一下,发现这里涉及到了编译器的优化。

我们可以看到,在第一次调用f的结束的时候,会返回一个由w构造的临时量,再将这个临时量作为实参来初始化第二个f的形参u,(第二个f调用到return这一步时,返回一个由w构造的临时量,再将这个临时量作为实参来初始化拷贝构造函数的形参)编译器觉得这一步有点多余,会将其优化为一步(第一次直接用w构造u,第二次直接用w构造y),所以每次调用f的时候其实只经过了3次的拷贝构造,最后再加上y的拷贝构造,一共是7次。


委托构造函数

概念

使用它所属类的其他构造函数执行它自己的初始化过程,换言之,它把它自己的一些(或者全部)指责委托给了其他构造函数。

形式

有一个成员初始值的列表和一个函数体。成员初始值列表只有一个唯一的入口,即类名本身。

class Date
{
public:// 非委托构造函数Date(int year, int month, int day):_year(year), _month(month), _day(day) {	}// 其余构造函数全部委托给上面的构造函数Date():Date(0,1,1){}Date(int i):Date(i,0,0){}Date(std::istream &is):Date(){is >> this->_year >> this->_month >> this->_day;}void pr(){cout << this->_year << ends << this->_month << ends << this->_day << endl;}
private:int _year;int _month;int _day;
};

本例中,共有四个构造函数。

第一个构造函数接受三个实参,使用这些实参初始化数据成员,然后结束工作。

第二个默认构造函数使用三参数的构造函数完成初始化过程,因为函数体为空可知无需再执行其他任务。

第三个构造函数接受一个实参,同样委托给了三参数的构造函数。

第四个构造函数先委托给了默认构造函数,默认构造函数又接着委托给了三参数构造函数。当这些受委托的构造函数执行完后,接着执行std::istream &is构造函数体的内容。

ps:当一个构造函数委托给另一个构造函数时,受委托的构造函数的初始值列表和函数体依次被执行,然后控制权才会交还给委托者的函数体。


匿名对象

当我们想要调用对象中的一个方法,或者只是想在这一句使用这个对象,其他地方不再使用该对象的时候。如果我们直接构造一个对象使用,这无疑是一种很大的浪费,因为这个对象我们用了一次就扔了,不再需要了,而一个对象的生命周期是整个栈帧。

这时就需要用到匿名对象,匿名对象是一种临时对象,它的生命周期只有使用它的那一行语句,执行完则立即销毁

class Date
{int _year;int _month;int _day;
public:Date(int year = 2020, int month = 4, int day = 24){_year = year;_month = month;_day = day;cout << "gouzao" << this << endl;}void print(){cout << this->_year << endl;}~Date(){cout << "xigou" << this << endl;}
};int main()
{Date d1; //创建一个对象,生命周期为整个函数栈帧d1.print();Date().print();//创建一个匿名对象,生命周期只有这一行语句,实行完则立即调用析构函数Date d2; //创建一个对象,生命周期为整个函数栈帧d2.print();return 0;
}

运行结果:
在这里插入图片描述


友元

类中的private部分非类的成员是无法访问的。但类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元(friend)。

  1. 友元函数可访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用const修饰
  3. 一个函数可以是多个类的友元函数
  4. 友元函数的调用与普通函数的调用和原理相同

友元的声明

友元声明只能出现在类定义的内部,但是在类内出现的具体位置不限。友元不是类的成员也不受它所在区域访问控制级别的约束。一般来说,最好在类定义开始或结束前的位置集中声明友元。

友元的声明仅仅指定了访问的权限,而非一个通常意义上的函数声明。如果我们希望类的用户能够调用某个友元函数,那么我们就必须在友元声明之外再专门对函数进行一次声明。

在这里插入图片描述
ps:上述代码想讲的还是要知道友元声明的作用是规定访问权限,不能起到普通意义声明的作用。

友元类

class A
{friend class Date;//将Date声明为友元类,Date可以访问A的所有成员变量
private:string str;
};
class Date
{
public:void Print(){cout << a.str << endl;//访问a的私有成员}
private:int _year;int _month;int _day;A a;
};

令成员函数作为友元

如果不想让整个类作为自己的友元,也可以只为那个需要访问自己的private对象的函数提供访问权限。当把一个成员函数声明成友元时,必须明确指出该成员函数属于哪个类:

class A;
class Date
{
public:void Print(A);private:int _year;int _month;int _day;
};class A
{friend void Date::Print(A);//将Date的成员函数Print声明为友元,Print可以访问A的所有成员变量
private:string str;
};void Date::Print(A a){cout << a.str << endl;//访问a的私有成员
}

要想令某个成员函数作为友元,我们必须仔细组织程序的结构以满足声明和定义的彼此依赖关系。该例中,我们必须按照如下方式设计程序:

  • 首先前向声明A类(因为Print的声明会用到)
  • 定义Date类,声明Print函数,但不能定义它(因为要访问A的私有成员,A还没有被定义好,无法访问其私有成员)
  • 定义A类,包括对Print的友元声明
  • 定义Print,此时它才可以使用A的成员

函数重载和友元

尽管重载函数的名字相同,但它们仍然是不同的函数。因此,如果一个类想把一组重载函数声明成它的友元,需要对这组函数中的每一个分别声明:
在这里插入图片描述
在A中并没有关于Print(A, A)的友元声明,因此其不能访问A的私有成员。

注意

1.友元关系是单向的,不具有交换性。
Date为A的友元,可以访问A的私有成员,但是A并不能访问Date的

2.友元关系不能传递。
如果B是A的友元,C是B的友元,则不能说明C时A的友元。


内部类

有没有想过,既然类的成员变量可以是自定义类型,那能不能在类中再构建一个类呢?

class Date
{
public:void Print(){cout << _year << endl;}private:class A{public:void Print(){cout << _data << endl;}private:int _data;};int _year;int _month;int _day;};

可以看到,这是可以的,但是这两个类有什么关系吗?

这个内部类其实是一个独立的类,它不属于外部类,同时外部类对它也没有任何特权,但是它同时还是外部类的友元类,可以通过外部类的对象参数来访问外部类的所有成员。

特性

  1. 内部类可以定义在外部类的public、protected、private都是可以的。
  2. 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
  3. sizeof(外部类)=外部类,和内部类没有任何关系

类的const成员

因为对于类和对象,封装性是一个很重要的东西,但是访问限定符只对外部有影响,对自身的成员函数没有影响,如果我们不想让一个成员函数对类的成员进行修改,这时,就需要用const来修饰成员函数。

class Date
{
public://等价于 void print(const Date* this)void Print() const{cout << _year << '-' << _month << '-' << _day << endl;}
private:int _year;int _month;int _day;};

对于用const修饰的成员函数,需要将const放在最后面,来区分开const参数和const返回值。这里的const其实修饰的是该成员函数的this指针,所以该成员函数就无法对类的成员进行修改。

  1. const对象可以调用非const成员函数吗?
    答案:不行,因为const对象的只能读不能写,而非const的成员函数则可读可写,使权限放大了,不行。

  2. 非const对象可以调用const成员函数吗?
    答案:可以,因为非const对象的可读可写,const成员函数只可读,使权限缩小,可行。

在这里插入图片描述

  1. const成员函数内可以调用其它的非const成员函数吗?
    答案:不行,因为const成员函数的this指针是常量,只可读,而非const的成员函数则可读可写,使权限放大了,不行。

  2. 非const成员函数内可以调用其它的const成员函数吗?
    答案:可以,因为非const成员函数的this指针可读可写,const成员函数只可读,使权限缩小,可行。

在这里插入图片描述

可变数据成员

有时会发生这样一种情况,我们希望能修改类的某个数据成员,即使是在一个const成员函数内。可以通过在变量的声明中加入mutable关键字实现这一目的。

一个可变数据成员永远不会是const,即使它是const对象的成员。因此,一个const成员函数可以改变成员的值。

举个例子,我们需要一个可变成员num来追踪每个成员函数被调用了多少次:

class Date
{
public:void Print() const{num++;cout << num << endl;}private:mutable size_t num = 0;
};int main(int argc, char const *argv[]) {Date d;d.Print();d.Print();d.Print();return 0;
}

运行结果:
在这里插入图片描述
尽管Print是一个const成员函数,它仍然能够改变num的值。因为num是个可变成员,因此任何成员函数,包括const函数在内都能改变它的值。


类的static成员

概念

有的时候类需要它的一些成员与类本身直接相关,而不是与类的各个对象保持关联。这样的成员叫做类的静态成员。


关于static

我们通过在成员的声明前加上关键字static使得其与类关联在一起。对于用static修饰的成员函数,称为静态成员函数,成员变量称为静态成员变量。因为静态的成员的生命域不在类中,在静态区,所以静态的成员只能在类外初始化。

  1. 静态成员为所有类对象所共享,不属于某个具体的实例
  2. 静态成员变量必须在类外定义,定义时不添加static关键字,一个静态数据成员只能定义一次
  3. 静态成员函数可以在类内部也可以在类外部定义,在类外部定义时,不能添加siatic关键字(外部有static关键字表示这个函数在文件内有效)
  4. 类静态成员亦可用 类名::静态成员 或者 对象.静态成员 或者 指向对象的指针->静态成员 来访问
  5. 静态成员函数不与任何对象绑定在一起,它们不包含this指针(static成员函数也就不能被声明成const,上章提到const是为了将this指针赋予底层const),也不能在static函数体内使用this指针,不包含this指针也就不能访问任何非静态成员
  6. 静态成员和类的普通成员一样,也有public、protected、private3种访问类别,也可以具有返回值
  7. 静态成员和全局变量虽然都存储在静态区,但是静态成员的生命周期只在本文件中,而全局变量不是

因为静态成员函数不属于某个对象,所以它没有this指针,无法访问任何非静态的成员,但是非静态的成员函数具有this指针,可以不用通过作用域运算符直接使用静态成员。

关于第二点和第三点的代码示例:

class Date
{int db = 666;static int num;static double init();
public:static int print();void test(){db += db * num;// 成员函数不用通过作用域运算符就能直接使用静态成员}
};
int Date::num = init(); // 定义并初始化一个静态成员int main(int argc, char const *argv[]) {int r;r = Date::print(); // 使用作用域运算符访问静态成员Date d1;Date *d2 = &d1;r = d1.print(); // 通过Date的对象或引用r = d2->print(); // 通过指向Date对象的指针return 0;
}

关于第二点:
在这里插入图片描述
从类名开始,这条定义语句的剩余部分就都位于类的作用域之内了。因此,我们可以直接使用init函数,虽然其是私有的。


静态成员的类内初始化

通常情况下,类的静态成员不应该在类的内部初始化。然而,我们可以为静态成员提供const整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的constexpr,初始值必须是常量表达式。

class Date
{constexpr static int num = 2*3; // num是常量表达式double db[num];
public:
};

如果某个静态成员的应用场景仅限于编译器可以替换他的值的情况,则一个初始化的const或者static constexpr不需要分别定义。相反,如果我们将它用于值不能替换的场景中,该成员必须有一条定义语句。

如果在类的内部提供了一个初始值,则在类外部成员的定义不能再指定一个初始值了:
在这里插入图片描述
即使一个常量静态数据成员在类内部被初始化了,通常情况下也应该在类的外部定义一下该成员。


静态成员能用于某些普通成员不能的场景

  1. 静态数据成员可以是不完全类型(声明之后定义之前)
  2. 静态数据成员的类型可以就是他所属的类类型。而非静态数据成员只能声明成他所属类的指针或引用
    在这里插入图片描述
  3. 可以使用静态成员作为默认实参
    在这里插入图片描述

非静态数据成员不可以,因为它的值本身属于对象的一部分这么做的结果是无法真正提供一个对象以便从中获取成员的值,最终将引发错误。
在这里插入图片描述
(图中error:非静态数据成员“Date::test”的使用无效)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/443822.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

顺序容器(vector、list、string、deque、forward_list)及迭代器、容器适配器

文章目录概述所有容器都支持的操作迭代器迭代器支持的操作迭代器支持的算术运算容器类型size_typeiterator 和 const_iterator容器定义和初始化拷贝初始化顺序容器独有的构造函数&#xff08;array除外&#xff09;array的初始化与内置数组类型的区别6种初始化方法&#xff08;…

jQuery实现表格隔行换颜色:

jQuery实现表格各行换颜色&#xff1a; 截图如下&#xff1a; 代码如下&#xff1a; <span style"font-family:Microsoft YaHei;font-size:14px;"><% page language"java" import"java.util.*" pageEncoding"UTF-8"%> &…

用stack处理中缀表达式【+、-、*、/、()】

文章目录题目描述思路使用getline()存储输入的字符串边读取边压栈完整代码题目描述 使用stack处理括号化的表达式。当你看到一个左括号&#xff0c;将其记录下来。当你在一个左括号之后看到一个右括号&#xff0c;从stack中pop对象&#xff0c;直至遇到左括号&#xff0c;将左括…

二维数组的查找

文章目录题目描述思路注意代码题目描述 在一个 n * m 的二维数组中&#xff0c;每一行都按照从左到右递增的顺序排序&#xff0c;每一列都按照从上到下递增的顺序排序。请完成一个高效的函数&#xff0c;输入这样的一个二维数组和一个整数&#xff0c;判断数组中是否含有该整数…

Springmvc,Spring MVC文件上传

Springmvc文件上传&#xff1a; 1.代码截图如下&#xff1a; 2.UploadController.java: package cn.csdn.controller;import java.io.File;import javax.servlet.http.HttpServletRequest;import org.springframework.stereotype.Controller; import org.springframework.ui.…

插入迭代器、流迭代器、反向迭代器、移动迭代器

文章目录前言插入迭代器inserterfront_inserterback_inserteriostream迭代器istream_iterator 读取输入流istream_iterator允许使用懒惰求值ostream_iterator操作反向迭代器reverse_iterator的base成员函数前言 除了为每个容器定义的迭代器之外&#xff0c;标准库在头文件iter…

泛型算法(lambda表达式、function类模板、bind函数适配器、迭代器类别、链表数据结构独有的算法)

文章目录概念find()函数迭代器令算法不依赖于容器但算法依赖于元素类型的操作算法永远不会执行容器的操作只读算法accumulate()函数从两个序列中读取元素&#xff08;equal函数为例&#xff09;迭代器作为参数形成两个序列equal()写容器元素的算法概念fill()fill_n()插入迭代器…

jsp,div 限制字数,超出部分用省略号代替

1.我是用struts2标签做的&#xff1a;如下&#xff1a; <% page language"java" import"java.util.*" pageEncoding"UTF-8"%> <% taglib prefix"s" uri"/struts-tags"%> <%String path request.getContext…

C++之关联容器

文章目录概述及类型mapsetpair类型概念初始化默认初始化提供初始化器允许的操作可以创建一个pair类的函数可以作为容器的类型关联容器迭代器概念map的迭代器set的迭代器是const的初始化map and setmultimap and multiset关联容器的操作额外的类型别名关联容器和算法删除元素添加…

动态内存、智能指针(shared_ptr、unique_ptr、weak_ptr)、动态数组

文章目录三种对象的分类三种内存的区别动态内存概念智能指针允许的操作智能指针的使用规范new概念内存耗尽/定位new初始化默认初始化直接初始化值初始化delete概念手动释放动态对象空悬指针shared_ptr类格式独有的操作make_shared函数shared_ptr的计数器通过new用普通指针初始化…

动态数组的简单应用

文章目录连接两个字符串字面常量题目注意代码输出结果处理输入的变长字符串题目注意代码连接两个字符串字面常量 题目 连接两个字符串字面常量&#xff0c;将结果保存在一个动态分配的char数组中。重写&#xff0c;连接两个标准库string对象。 注意 使用头文件cstring的str…

二分查找算法实现

文章目录思路代码以二分区间作为while判定条件以给定值作为while判定条件主函数思路 while判定条件的选择&#xff0c;注意最外层的return的内容就好。下面提供了两个代码版本。计算 mid 时需要防止溢出&#xff08;对应类型【如本例中的int】可能存不下&#xff09;&#xff…

根据中序、前序遍历重建二叉树

文章目录题目递归思路细节易错代码复杂度分析迭代思路细节易错代码复杂度分析题目 输入某二叉树的前序遍历和中序遍历的结果&#xff0c;请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 例如&#xff0c;给出 前序遍历 preorder [3,9,20,15,7] 中…

深搜+剪枝

文章目录题目思路注意代码复杂度分析题目 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c…

搜索+回溯问题(DFS\BFS详解)

文章目录题目思路DFS思路代码复杂度分析BFS思路代码复杂度分析题目 地上有一个m行n列的方格&#xff0c;从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动&#xff0c;它每次可以向左、右、上、下移动一格&#xff08;不能移动到方格外&#xff09;&am…

快速幂实现pow函数(从二分和二进制两种角度理解快速幂)

文章目录迭代实现快速幂思路int的取值范围快速幂从二进制的角度来理解从二分法的角度来理解代码复杂度分析进阶——超级次方思路倒序快速幂正序快速幂代码复杂度分析迭代实现快速幂 实现 pow(x, n) &#xff0c;即计算 x 的 n 次幂函数&#xff08;即&#xff0c;xn&#xff0…

n位数的全排列(需要考虑大数的情况)

文章目录题目思路代码题目 输入数字 n&#xff0c;按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3&#xff0c;则打印出 1、2、3 一直到最大的 3 位数 999。 示例 1: 输入: n 1 输出: [1,2,3,4,5,6,7,8,9] 说明&#xff1a; 用返回一个整数列表来代替打印 n 为正整数 …

正则表达式匹配(动规)

文章目录题目思路转移方程特征再探 i 和 j代码题目 请实现一个函数用来匹配包含 . 和 * 的正则表达式。模式中的字符 . 表示任意一个字符&#xff0c;而 * 表示它前面的字符可以出现任意次&#xff08;含0次&#xff09;。在本题中&#xff0c;匹配是指字符串的所有字符匹配整…

在循环递增一次的数组中插入元素

文章目录题目思路如何建立左右区间&#xff1f;如何查找最高点&#xff1f;那我们怎么判断 num 到底处于什么样的位置呢&#xff1f;如何确定插入位置&#xff1f;插入元素代码题目 给一个只循环递增一次的数组 res&#xff0c;res 满足首元素大于等于尾元素&#xff0c;形如&…

表示数值的字符串(有限状态自动机与搜索)

文章目录题目思路一代码一思路二代码二题目 思路一 考察有限状态自动机&#xff08;参考jyd&#xff09;&#xff1a; 字符类型&#xff1a; 空格 「 」、数字「 0—9 」 、正负号 「 」 、小数点 「 . 」 、幂符号 「 eE 」 。 状态定义&#xff1a; 按照字符串从左到右的…