C++ primer第六章6.5函数的学习 之特殊用途的语言特性

6.5.1 默认实参

  • 将反复出现的数值称为函数的默认实参,调用含有默认实参的时候可以包含该实参也可以不包含
  • 比如程序打开页面会有一个默认的宽高,如果用户不喜欢也允许用户自由指定与默认数值不同的数值,具体例子如下图所示
typedef string::size_type sz;
string screen(sz ht = 24,sz wid = 80,char backgrnd = ' ');
  • 默认实参作为形参的初始化数值出现在形参列表中,可以为一个或者多个形参定义成默认数值,但是,一旦某个形参被赋予了默认数值,这个形参之后的数值也必须有默认数值。

使用默认实参调用函数

  • 如果使用默认的实参,调用函数的时候省略掉该实参就可以了。
  • 如上面提出的screen函数,有三个变量,因此可以使用0、1、2、3个实参调用该函数
typedef string::size_type sz;
string screen(sz ht = 24,sz wid = 80,char backgrnd = ' ');
int main(){string window;window = screen();window = screen(1);window = screen(1,2);window = screen(1,2,'&');
}
  • window = screen(,,'&');//错误,只可以忽略掉尾部的实参,即左边的必须补齐定义,只有右边的才可以缺省
  • 因此,在实际使用的场景中,需要设计形参的顺序,将不怎么使用默认形参的变量放在前面

默认实参声明

  • 对于函数的声明来讲,通常是将其放在头文件中,并且每个函数声明一次,但是多次声明也是合法的。
  • 但是,在给定的作用域中,一个形参只可以赋予一次默认实参。即,函数的后续声明只能为之前那些没有默认值的形参添加默认的实参,而且这个形参的左侧的所有的形参都有默认值。

默认实参初始值

  • 局部变量不可以作为默认的实参,只要表达式的类型可以转化为形参所需要的类型,该表达式就可以作为默认的实参。
typedef string::size_type sz;
sz wd = 80;
char def = ' ';
sz ht();
string screen(sz = ht(),sz = wd,char = def);
string window = screen();//调用screen(ht(),80,' ')
  • 用作默认实参的名字,在函数声明所在的作用域中解析,而这些名字的求值的过程发生在函数调用的时候
void f2(){def = '*';//改变默认的实参的数值sz wd = 100;//隐藏了外层定义的wd,但是没有改变默认的数值window = screen();//调用了screen(ht(),80,'*')
}
  • 在函数f2的内部改变了def的值,所以对于screen的调用将会传递这个更新过的数值。
  • 对于wd,内部声明的局部变量会隐藏外部的wd,但是该局部变量并不会传递给screen的默认的参数

6.5.2 内联函数和constexpr函数

定义函数的好处

  • 使用函数可以确保行为的统一,每次相关的操作都按照同样的方式进行
  • 如果需要修改计算的过程,显然修改函数比先找到等价的表达式所有出现地方再逐一修改更加方便
  • 函数可以被其他应用重复调用,省去了重新编写开发的代价

坏处

  • 调用函数会比使用表达式慢,因为使用函数要涉及到一系列的工作:调用前保存寄存器、并且在返回的是会恢复;需要拷贝实参,程序需要在不同的新的位置上继续执行。

内联函数可以避免函数的调用所带来的开销

  • 将函数指定为内联函数,通常是将它在每一个调用点上“内联地”展开,将会减少函数运行使得开销。
  • 在函数的前面加上关键字inline就可以将其声明称为内联函数

inline const string &shorterString(const string &s1,const string &s2){return s1.size() < s2.size() ? s1 : s2;
}
  • 内联说明只是向编译器发出一个请求,编译器可以选择忽略这个请求
  • 内联机制一般用于优化规模较小,流程直接、频繁调用的函数,很多编译器都不支持内联递归函数,如果程序很大的话,也不大可能在调用点内联的展开

constexpr函数

  • constexpr是指可以用于常量表达式的函数。
  • 其函数的返回类型以及所有的形参的类型都得是字面值类型,而且函数体中必须有且只有一个return语句。
constexpr int new_sz() {return 42;}
constexpr int foo = new_sz(); //正确,foo是一个常量的表达式
  • 把new_sz定义成无参数的constexpr函数,因为编译器能在程序编译的时候验证new_sz函数返回的是常量的表达式,所以可以使用new_sz来初始化foo函数。
  • 执行该初始化任务的时候,编译器把constexpr函数的调用替换成其结果数值。为了能在编译的过程中随时展开,constexpr会被隐式转变为内联函数。
  • constexpr函数体内也可以包含其他语句,只要这些语句在运行的时候不执行任何操作就可以。例如,constexpr函数内可以有空的语句、类型别名以及using声明。
  • 允许constexpr函数的返回值并非一个常量。
//如果arg是常量的表达式,则scale(arg)也是常量的表达式
constexpr size_t scale(size_t cnt){return new_sz() * cnt;//当scale的实参是常量表达式的时候,返回的数值也是常量的表达式;反之不然;
}
int main(){int arr[scale(2)];//正确,scale(2)是常量的表达式int i = 2;int a2[scale(i)];//错误,scale(i)不是常量的表达式
}

把内联函数和constexpr函数放在头文件中

  • 和其他函数不一样,内联函数和constexpr函数可以在程序中多次定义。毕竟,编译器要想展开函数仅仅有函数的声明是不够的,还需要函数的定义。但是对于某个给定的内联函数或者constexpr函数来说,他的多个定义必须完全一致,因此通常将他们定义在头文件中。

6.5.3 调试帮助

  • C++有些时候会用到一些类似于头文件保护的技术,从而可以有选择的执行代码。
  • 基本思想是,程序包含一些用于调试的代码,但是这些代码只会在开发程序的时候使用,当程序完准备上线的时候,需要先屏蔽代码,这就用到了两项预先处理的功能:assert和NDEBUG

assert预处理宏

  • 预处理宏就是一个预处理变量,其行为类似于内联函数
  • assert使用一个表达式作为它的条件 assert(expr);  首先对于expr进行求值,如果表达式为假,assert会输出信息并且终止程序的执行。如果表达式为真,assert什么也不做
  • assert的头文件会定义在cassert文件中,因为预处理名字是由预处理器而不是编译管理,因此可以直接使用预处理名字而不需要提供using声明。即直接使用assert,而不是std::assert,也不需要为assert提供using的声明。
  • 和预处理的变量一样,宏名字在程序内部必须唯一。但是在实际编程的时候,即使没有包含这个头文件,也不要使用assert这个名字进行任何相关的变量、函数、其他实体等的命名,因为,很多头文件都会包含这个cassert,有可能通过其他途径包含在程序中。
  • assert宏常常用于检查“不能发生的条件”。例如,一个对于输入文本进行操作的语句对于输入的长度有很大的要求,必须大于某一个指定的阈值。这个使用,程序可能会包含一个如下所示的语句  assert(word.size() > threshold );
  • assert都是一种调试程序的辅助手段,但是他不可以替代运行时候的逻辑检查,也不可以替代程序本身应该包含的错误检查

NDEBUG预处理变量

  • assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NDEBUG,那么assert就什么都不做。默认情况下是没有定义这个NDEBUG的,因此assert会执行运行检查。
  • 使用#define NDEBUG 或者使用命令行进行程序编译的时候,使用 CC -D NDEBUG main.c 这两者之间是等效的
  • 同理,也可以使用NDEBUG来编写自己的条件调试的代码,如果NDEBUG没有定义,那么将会执行#ifdef和#endif之间的代码;如果定义了NDEBUG,那么这些代码将会被忽略掉
  • #define NDEBUG
#ifdef NDEBUG// __func__是编译器定义的一个局部静态变量,用于存放函数的名字cerr << __func__ << ": array size is " << size << endl;
#endif
  • __func__是一个const char的一个静态数组,用于存放函数的名字

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

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

相关文章

Android设计模式之——Builder模式

一、介绍 Builder模式是一步一步创建一个复杂对象的创建型模式&#xff0c;它允许用户在不知道内部构建细节的情况下&#xff0c;可以更精细的控制对象的构造流程。该模式是为了将构建复杂对象的过程和它的部件解耦&#xff0c;使得构建过程和部件的表示隔离开来。 因为一个复…

c++后端开发书籍推荐

推荐书籍: 略读80% 精读50% C&#xff1a; C Primer Plus C和指针&#xff08;入门书 不只是指针&#xff09; C陷阱与缺陷&#xff08;宏相关&#xff09; C专家编程 C&#xff1a; 有专门的视频 C primer C程序设计原理与实践&#xff08;c之父写的 入门经典&#xff09; Ef…

C++ primer第六章6.6函数匹配

函数的匹配 当重载函数的形参数量相等以及某些形参的类型可以由其他的类型转化得来的时候&#xff0c;对于函数的匹配就会变得很难 确定候选函数和可行函数 函数匹配的第一步就是选定本次调用对应的重载函数集&#xff0c;集合中的函数称为候选函数。候选函数具有两个特征&am…

Android设计模式之——原型模式

一、介绍 原型模式是一个创建型的模式。原型二字表明了该模型应该有一个样板实例&#xff0c;用户从这个样板对象中复制出一个内部属性一致的对象&#xff0c;这个过程也就是我们俗称的“克隆”。被复制的实例就是我们所称的“原型”&#xff0c;这个原型也是可定制的。原型模…

C++ primer第六章6.7函数指针

函数指针 函数指针指向的是函数而不是对象。和其他指针一样&#xff0c;函数指针指向某种特定的类型。函数的类型由他的返回类型和形参类型共同决定&#xff0c;而与函数的名字无关。 //比较两个string对象的长度 bool lengthCompare(const string &,const string &);…

Android设计模式之——工厂方法模式

一、介绍 工厂方法模式&#xff08;Factory Pattern&#xff09;&#xff0c;是创建型设计模式之一。工厂方法模式是一种结构简单的模式&#xff0c;其在我们平时开发中应用很广泛&#xff0c;也许你并不知道&#xff0c;但是你已经使用了无数次该模式了&#xff0c;如Android…

C++ primer第十八章 18.1小结 异常处理

18.1 异常处理 异常处理机制&#xff0c;允许程序独立开发的部分能够在运行的时候出现的问题进行通信并且做出相应的处理&#xff0c;异常的处理使得我们可以将问题的检测和处理分离开来。程序的一部分负责检测问题的出现&#xff0c;然后将解决这个问题的任务传递给程序的另一…

浅谈equals与==

一、前言 示例代码&#xff1a; public static void main(String[] args) throws IOException {String str1 new String("hello");String str2 new String("hello");String str3 "cde";String str4 "cde";int i1 3;int i2 3;In…

针对C++异常的学习

源码 头文件 sdf_exception.h #pragma once#include <exception> #include <string>namespace sdf {namespace common{using sdf_error_code_t uint32_t;class SdfException : std::exception{public:explicit SdfException(sdf_error_code_t errorCode) : erro…

Android设计模式之——抽象工厂模式

一、介绍 抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;&#xff0c;也是创建型设计模式之一。前一节我们已经了解了工厂方法模式&#xff0c;那么这个抽象工厂又是怎么一回事呢&#xff1f;大家联想一下现实生活中的工厂肯定都是具体的&#xff0c;也就是说…

Android设计模式之——策略模式

一、介绍 在软件开发中也常常遇到这样的情况&#xff1a;实现某一个功能可以有多种算法或者策略&#xff0c;我们根据实际情况选择不同的算法或者策略来完成该功能。例如&#xff0c;排序算法&#xff0c;可以使用插入排序、归并排序、冒泡排序等。 针对这种情况&#xff0c;…

密码学在区块链隐私保护中的应用学习

身份隐私保护技术 混淆服务 混淆服务的目的在于混淆消息双方的联系&#xff08;如 图 2 所示&#xff09;。当发送方需要告知接收方消息 M 时&#xff0c; 它会首先用接收方的公钥 KB 加密 M&#xff0c;并在密文后 附带真实接收地址 R。为了借助第三方&#xff08;图 2 中的…

值类型和引用类型的区别

一、定义 引用类型表示你操作的数据是同一个&#xff0c;也就是说当你传一个参数给另一个方法时&#xff0c;你在另一个方法中改变这个变量的值&#xff0c;那么调用这个方法是传入的变量的值也将改变。 值类型表示复制一个当前变量传给方法&#xff0c;当你在这个方法中改变…

面向区块链的高效物化视图维护和可信查询论文学习

物化视图介绍 如何维护物化视图仍旧是一个开放问题.在关系数据库中,增量刷新的物化视图维护策略可划分为立即维护和延迟维护两大类.立即维护策略的优点是实现较为简单,在单数据源下不 存在一致性问题;然而该策略将物化视图维护过程嵌入到更新事务之中,延长了更新事务的提交时间…

Java基础知识(一)

一、接口 类描述了一个实体&#xff0c;包括实体的状态&#xff0c;也包括实体可能发出的动作。 接口定义了一个实体可能发出的动作。但是只是定义了这些动作的原型&#xff0c;没有实现&#xff0c;也没有任何状态信息。 所以接口有点象一个规范、一个协议&#xff0c;是一个…

密码学数字信封的介绍

对称密码和非对称密码 对称密码&#xff1a;加解密运算非常快&#xff0c;适合处理大批量数据&#xff0c;但其密码的分发与管理比较复杂非对称密码&#xff1a;公钥和私钥分离&#xff0c;非常适合密钥的分发和管理 数字信封的定义 如果将对称密码算法和非对称密码算法的优点…

Android设计模式之——状态模式

一、介绍 状态模式中的行为是由状态来决定的&#xff0c;不同的状态下有不同的行为。状态模式和策略模式的结构几乎完全一样&#xff0c;但它们的目的、本质却完全不一样。状态模式的行为是平行的、不可替换的&#xff0c;策略模式的行为是彼此独立、可相互替换的。用一句话来…

Android设计模式之——责任链模式

一、介绍 责任链模式&#xff08;Iterator Pattern&#xff09;&#xff0c;是行为型设计模式之一。什么是”链“&#xff1f;我们将多个节点首尾相连所构成的模型称为链&#xff0c;比如生活中常见的锁链&#xff0c;就是由一个个圆角长方形的铁环串起来的结构。对于链式结构…

目前基于区块链的档案防篡改系统的设计如何实现防篡改

架构设计图 分析 为了保障档案数据的安全性和隐私性&#xff0c;存储档案附件和档案属性存储加密存储在私有IPFS集群&#xff0c;档案的IPFS地址和数字指纹存储在私有区块链上。公有区块链定期存储和检查私有区块链最新不可逆区块的高度和哈希值&#xff0c;以保障私有区块链上…

IPFS的文件存储模式

IPFS是如何进行文件存储的 IPFS采用的索引结构是DHT&#xff08;分布式哈希表&#xff09;&#xff0c;数据结构是MerkleDAG&#xff08;Merkle有向无环图&#xff09; DHT(分布式哈希表) 参考链接MerkleDAG&#xff08;Merkle有向无环图&#xff09; 参考链接MerkleDAG功能…