C++Primer学习笔记:第6章 函数

  • 通过调用运算符()调用函数

  • 函数的调用完成两项工作:

    • 用实参初始化函数对应的形参
    • 将控制权转移给被调用函数:主调函数的执行被暂时中断,被调函数开始执行
  • 尽管实参与形参存在对应关系,但是并没有规定实参的求值顺序。编译器能以任意可行的顺序对实参求值

  • 任意两个形参都不能同名,形式参数名是可选的,但是由于我们无法使用未命名的形式参数,所以形式参数一般都应该有一个名字

  • 函数的返回类型不能是数组类型或者函数类型,但可以是指向数组或者函数的指针

  • 函数体必须是大括号包围的!

  • 普通局部变量对应的对象是自动对象:当函数的控制路径经过变量定义语句时创建该对象,当到达定义所在的块末尾时销毁它。

  • 局部静态对象:在程序的执行路径第一次经过对象定义时对它初始化,并且直到程序终止时才被销毁,在此期间即使对象所在的函数执行结束也不会对他有影响。如果局部静态变量没有显式的初始值,则执行值初始化(内置类型会初始化为0)

  • 函数只能定义一次,但是可以声名多次,唯一的区别是函数的声明不需要函数体,用一个分号替代即可(因此经常省略形式参数的名字,但是写上名字也有利于理解函数的功能),函数声明也称作函数原型

  • 形式参数初始化的机理和变量初始化一样

  • 拷贝大的类类型对象或者容器对象比较低效,甚至有的类型(包括IO类型在内)根本不支持拷贝操作。因此函数只能通过引用形式参数访问该类型的对象,如果函数无须改变引用形式参数的值,最好将其声明为常量引用

  • 熟悉C的程序员常常使用指针类型的形参访问函数外部的对象,在C++中最好还是使用引用类型的形式参数代替指针

  • 对于有可能是临时参数的形式参数,我们不应该使用引用(因为无法引用到常量上)

  • 函数重载要求同名函数的形式参数列表应该有明显的区别,因此如果仅仅是const的不同则不能进行重载(应该要求形式参数类型不同)

  • 形式参数的初始化方式和变量的初始化方式是一样的:我们可以使用非常量初始化一个底层const对象,但是反过来不行,同时一个普通的引用必须使用同类型的对象初始化(详细同第二章指针和引用部分)

  • 尽量使用常量引用

    • 给函数调用者传递正确的信息
    • 使用非常量引用会极大地限制函数所能接受的实际参数类型:我们不能把const对象、字面值对象或者需要类型转换的对象传递给普通的引用参数
  • 数组形式参数

    • 数组的两个特点:

      • 不允许拷贝数组
      • 使用数组时通常会将其转换成指针
    • 以下三种声明方式是等价的:

      void print(const int*);
      void print(const int[]);
      void print(const int[10]);
      
    • 因为我们不清楚数组的实际大小,因此在使用过程中必须通过一定的方式判断是否越界

      • 使用标记指定数组长度:例如C风格的字符串,最后一个一定是一个\0,我们可以判断是否为\0来判断是否到达末尾

      • 使用标准库规范:

        void print(const int *beg, const int *end) {while(beg != end) {cout << *beg++ << endl;}
        }
        int arr[] = {0, 1, 2};
        print(begin(arr), end(arr));	//#include<iterator>
        
      • 显式传递一个表示数组大小的形式参数

      • 同常量引用,当函数不需要对数组元素执行读写操作的时候,数组形式参数应该是指向const的指针

    • 数组引用参数

      void print(int (&arr)[10]) {for (auto item : arr) {cout << item << endl;}
      }
      

      对于数组的引用详细可以看第三章关于数组部分的笔记

    • 传递多维数组:C++语言中实际上没有真正的多维数组,所谓的多维数组其实是数组的数组。数组第二维(以及后面所有的维度)的大小都是数组类型的一部分,不能省略

      void print(int (*matrix)[10], int rowSize);
      void print(int matrix[][10], int rowSize);
      

      上面两种声明是完全等价的

  • 命令行选项可以通过两个(可选的)形式参数传递给main函数:

    int main(int argc, char *argv[]);
    int main(int argc, char **argv);
    
    • 第二个形式参数是一个数组,它的元素指向C风格字符串的指针,第一个形式参数argc表示数组中字符串的数量
    • argv第一个元素指向程序的名字或者一个空字符串,接下来的元素依次传递命令行提供的参数,最后一个指针的元素值保证为0
  • 为了编写能处理不同数量实际参数的函数,C++11标准提供了几种方法:

    • 如果函数的实际参数数量未知但是类型相同,我们可以使用initializer_list类型的形式参数(需要#include<initializer_list

      initializer_list<T> lst;	//默认初始化,T类型元素的空列表
      initializer_list<T> lst{a,b,c...};	//lst的元素是对应初始值的副本,列表中的元素是const
      lst2(lst);	 //等价与lst2 = lst ,赋值,不会拷贝列表中的元素,原始列表和副本共享元素
      lst.size()		//列表中的元素数量
      lst.begin()
      lst.end()
      

      initialzer_list对象中的元素永远是常量值

      void err_msg(initializer_list<string> il) 
      {for (auto beg = il.begin(); beg != il.end(); ++beg)cout << *beg << " ";	//也可以通过范围for循环访问65cout << endl;
      }
      err_msg({"A", "B", "C"});
      err_msg({"A", "B"});
      
    • 使用可变参数模板

  • 返回void的函数不要求非得有return语句,因为在这类函数的最后一句会隐式地执行return,一个返回类型是void的函数也能使用return expression,不过此时return语句的expression必须是另一个返回void的函数,强行令void函数返回其他类型将产生编译错误

  • 有返回值函数

    bool str_subrange(const string &str1, const string &str2) 
    {auto size = (str1.size() < str2.size()) ? str1.size() : str2.size();for (decltype(size) i = 0; i < size; ++i) {if (str1[i] != str2[i])return false;}return true;
    }
    
  • 返回一个值的方式和初始化一个变量或形式参数的方式完全一样:返回的值用于初始化调用点的一个临时量,这个临时量就是函数调用的结果

  • 不要返回局部对象的引用或者指针:函数完成后,它所占用的存储空间也随之被释放掉。因此,函数终止意味着局部变量的引用(或指针)将指向不再有效的内存区域

  • 调用一个返回引用的函数得到左值,其他返回类型得到右值

  • C++11新标准规定,函数可以返回花括号包围的值的列表。类似于其他返回结果,此处的列表也用来对表示函数返回的临时量进行初始化

  • 我们允许main函数没有return语句直接结束,如果控制到达了main函数的结尾处而且没有return语句,编译器将隐式地插入一条返回0的return语句。在cstdlib头文件中定义了两个预处理变量,我们用这两个变量分别表示成功与失败

    int main()
    {if (some_failure) {return EXIT_FAILURE;	} else {return EXIT_SUCCESS;}
    }
    
  • 从语法上来说,想要定义一个返回数组的指针或引用的函数比较繁琐,但是使用类型别名可以简化这一任务

    typedef int arrT[10];	//using arrT = int[10];
    arrT* func(int i);		//返回一个指向含有10个整数的数组的指针
    int (*func(int i))[10];	//等价于上面的声明
    

    我们还可以使用尾置返回类型使得上面的声明变得清晰:

    auto func(int i) -> int(*)[10];
    

    如果我们知道函数返回的指针指向哪个(类别)的数组,我们还可以使用decltype关键字声明返回类型

    int arr[] = {0, 1, 2, 3, 4};
    decltype(arr) *arrPtr(int i) 
    {return &arr;    
    }
    
  • 如果同一作用域内的几个函数名字相同但是形式参数列表(形式参数数量或形式参数类型)不同,我们称之为重载函数。main函数不能重载。需要注意的是,函数的重载和返回类型关系不大

  • 顶层const不影响传入函数的对象,一个拥有顶层const的形式参数无法和另一个没有顶层const的形式参数区分开来。但是底层const是会影响函数的重载的,当传入的对象是常量时,会选择带有底层cosnt的函数版本,如果传递一个非常量对象,编译器会优先选用非常量版本的函数

  • 最好只重载那些确实非常相似的操作

  • 我们也可以使用const_cast实现const到非const的转换:

    const string &func(const string &s1, const string &s2)
    {return s1.size() < s2.size() ? s1 : s2;
    }
    string &func(string &s1, string &s2)
    {return const_cast<string&>(func(const_cast<const string&>(s1), const_cast<const string&)(s2));
    }
    
  • 当调用重载函数时的结果:

    • 编译器找到一个与实际参数最佳匹配的函数
    • 找不到任何一个函数匹配,发出无匹配的错误
    • 有多于一个函数可以匹配,但是没一个都不是明显的最佳选择,此时也将发生错误,称为二义性调用
  • 如果我们在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体。在不同的作用域中无法重载函数名

  • 一旦某个形式参数被赋予了默认值,它后面所有形式参数都必须有默认值。当设计含有默认实际参数的函数时,其中一项任务是合理设置形式参数顺序,尽量让不怎么使用默认值的形式参数出现在前面

  • 通常,应该在函数声明中指定默认实际参数,并将声明放在合适的头文件中。局部变量不能作为默认实际参数

  • 用作默认实际参数的名字在函数声明所在的作用域内解析,而这些名字的求值过程发生在函数调用时。比如函数A某个默认实际参数的值是一个函数B调用的返回值,则该函数调用B会在A被调用的时候调用

    #include <iostream>using namespace std;string A = "global A";
    string B = "global B";const string &func()
    {return const_cast<const string&>(B);
    }int main() 
    {ios::sync_with_stdio(false);void test(const string &a = A, const string &b = func());string A = "local A";   //local varibale cannot be default value::A += " has been changed";B = "local B";test();return 0;
    }void test(const string &a, const string &b)
    {cout << a << endl;cout << b << endl;
    }

    运行结果:

    global A has been changed
    local B
    
  • 将一些简单但需要多次重复的函数定义为内联函数的好处:

    • 有利于阅读理解
    • 可以被重复利用,使得代码简洁
    • 需要修改时只用修改一个地方
  • 在函数前面加上inline便可以将函数生命为内联函数。内联说明只是向编译器发出一个请求,编译器可以选择忽略这个请求。一般来说,内联机制用于优化规模较小、流程直接、频繁调用的函数。很多编译器都不支持内联递归函数

  • constexpr函数是指能够用于常量表达式的函数

    • 函数的返回类型以及所有形式参数的类型都必须是字面值类型,而且函数体中必须有且只有一条return语句
    • constexpr函数被隐式地指定为内联函数
    • 允许constexpr函数的返回值不是一个常量,如果参数非常量表达式导致最后返回值不是常量表达式则在需要常量的地方调用会报错
  • 内联(inline)函数和constexpr函数可以在程序中多次定义,但是多个定义必须一致。基于这个原因,内联函数和constexpr函数通常定义在头文件中

  • 程序可以包含一些用于调试的代码,但是这些代码只在开发程序时使用。当应用程序编写完成准备发布时,要先屏蔽掉调试代码

    • assert (expr)预处理宏:首先对expr求值,如果表达式为假(0),assert输出信息并终止程序的执行,如果为真,则什么也不做
      • 需要头文件cassert,因为是供预处理器处理,所以无需提供using声明
      • assert宏常用于检查“不能发生”的条件,即程序的运行是建立在assert的条件成立的情况下
    • assert的行为依赖于一个名为NDEBUG的预处理变量的状态,如果定义了NDEBUG,则assert什么也不做。默认情况下没有定义NDEBUG,此时assert将执行检查。如果想要关闭assert检查:
      • 在程序开头加上#define NDEBUG
      • 或在编译的时候加上-D NDEBUG参数
    assert(word.size() >=  threshold);
    //等价写法:
    #ifndef NDEBUG
    if (word.size() < threshold)cerr << "Error: " << __FILE__<< " : in function " << __func__<< " at line " << __LINE__ << endl<< "	Compiled on " << __DATE__<< " at " << __TIME__ << endl<< "	Word read was \"" << word<< "\": Length too short" << endl;
    #endif
    
  • 函数匹配

    • 选定候选函数:
      • 与被调用函数同名
      • 其声明在调用点可见
    • 选定可行函数:
      • 形式参数和实际参数数量一直
      • 类型符合(相同或可以进行转换)
    • 寻找最佳匹配
      • 该函数的每个实际参数的匹配不劣于其他可行函数需要的匹配
      • 至少有一个实际参数的匹配优于其他可行函数提供的匹配
    • 如果最终确定了一个函数,则匹配成功,如果最后匹配出多个函数,则匹配失败,报告二义性错误
  • 为了确定最佳匹配,编译器将实际参数类型到形式参数类型的转换分成了几个等级:

    1. 精确匹配
      • 实际参数类型和形式参数类型相同
      • 实际参数从数组类型或函数类型转换成相应的指针类型
      • 向实际参数添加或者删除顶层const
    2. 通过const转换实现的匹配
    3. 通过类型提升实现的匹配(小整数类型会自动变成int,如果放不下再变成unsigned int
    4. 通过算数类型转换或指针转换实现的匹配
    5. 通过类类型转换实现的匹配
  • 想要声明一个指向函数的指针,只需要用指针替换函数名即可:

    bool func(const string &, const string &);
    bool (*pf)(const string &, const string &);
    

    当我们把函数名作为一个值使用时,该函数自动地转换成指针

    pf = func;
    //等价于
    pf = &func;	//&是可选的
    

    我们可以直接使用指向函数的指针调用该函数,无需提前解引用指针

    //等价的三种调用方法
    bool b1 = pf("A", "B");
    bool b2 = (*pf)("A", "B");
    bool b3 = func("A", "B");
    
  • 在指向不同函数类型的指针之间不存在转换规则,但是我们可以为函数指针赋一个nullptr或者0

  • 当我们使用重载函数为指针赋值时,上下文必须清晰地界定到底应该选用哪个函数

  • 我们可以定义函数指针作为形式参数

    void work(bool pf(const string &, const string &));	//看起来是函数类型,实际上会自动转换成指针
    //等价于
    void work(bool (*pf)(const string &, const string &));
    

    我们同样可以使用typedefdecltype简化操作

    typedef bool funcT(const string &, const string &);	//funcT是函数类型
    typedef decltype(func) funcT2;	//同上
    typedef bool (*funcTP)(const string &, const string &);	//funcTP是函数指针
    typedef decltype(func) *funcTP2;	//同上
    void work(funcT);	//同之前定义,函数类型会自动转换成指针类型
    void work(funcTP);	//同之前定义
    
  • 编译器不会自动地将函数返回类型当成对应的指针类型进行处理

    using F = int(int *, int);
    using FP = int(*)(int *, int);
    //以下四种方式是等价的
    FP f1(int);
    F *f1(int);
    int (*f1(int))(int *, int);
    auto f1(int) -> int (*)(int *, int);
    

    如果使用decltype指定返回函数指针类型记得decltype(func)如果func是一个函数则得到的是函数类型,还需要加上*

    decltype(func) *getFunc(const string &);
    

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

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

相关文章

C++Primer学习笔记:第8章 IO库

C语言不直接处理输入输出&#xff0c;而是通过一族定义在标准库中的类型来处理IO iostream定义了用于读写流的基本类型fstream定义了读写命名文件的类型sstream定义了读写内存string对象的类型 标准库使得我们能够忽略这些不同类型的流之间的差异&#xff0c;是通过继承机制实…

C++Primer学习笔记:第7章 类

类的基本思想是数据抽象data abstraction和封装encapsulation。数据抽象是一种依赖于接口interface和实现implementation分离的编程技术 在类中&#xff0c;由类的设计者负责考虑类的实现过程&#xff0c;使用该类的程序员只需要抽象地思考类型做了什么&#xff0c;而无须了解…

每日一题:leetcode191.位1的个数

题目描述 题目分析 很自然地想到了二进制枚举&#xff0c;直接循环检查每一个二进制位。 class Solution { public:int hammingWeight(uint32_t n) {int ret 0;uint32_t t 1;for (int i 0; i < 32; i, t << 1) {if (n & t) {ret;}}return ret;} };AC之后看了…

每日一题:leetcode341.扁平化嵌套列表迭代器

题目描述 题目分析 这个题目自己大概花了一个小时&#xff0c;虽然是一遍AC&#xff0c;但是速度有点慢&#xff0c;太长时间不写代码导致自己对代码不太敏感&#xff0c;写起来慢腾腾的。 看到这个的想法就是&#xff0c;要用栈来保存列表的迭代器&#xff0c;这样将孩子列表…

每日一题:leetcode82. 删除排序链表中的重复元素 II

题目描述 题目分析 这才是正常的中等题难度嘛&#xff0c;昨天的中等题题解我半天看不懂。。。 首先&#xff0c;需要增加一个哑节点&#xff08;操作链表的常规操作&#xff09;&#xff0c;因为有可能删除首节点&#xff0c;我们不想要为首节点添加单独的逻辑。其次&#xf…

每日一题:leetcode456.132模式

题目描述 题目分析 我觉得这道题应该是我做过最难的中等题之一了&#xff0c;这是昨天的每日一题&#xff0c;但是昨天用nlogn的做法做出来以后在看题解&#xff0c;发现有些看不懂&#xff08;觉得题解有点故弄玄虚&#xff09;。然后今天中午又花了一点时间才搞懂&#xff0…

leetcode283.移动零

题目描述 题目分析 在写简单题放松&#xff0c;看到这道题第一个想法是用STL库函数&#xff0c;虽然知道大概要用双指针之类的&#xff0c;但是库函数爽哇。 class Solution { public:void moveZeroes(vector<int>& nums) {stable_sort(nums.begin(), nums.end(), …

每日一题:leetcode61.旋转链表

题目描述 题目分析 很容易发现&#xff0c;如果k是n的整数倍&#xff0c;相当于没有移动。这样直接对k%n使得k在一个可以接受的范围。 因为是顺序移动&#xff0c;各元素之间的相对位置保持不变&#xff0c;所以就想着将链表先变成一个环。然后再移动头指针&#xff0c;最后再…

每日一题:leetcode173.二叉搜索树迭代器

题目描述 题目分析 更加地觉得编程重要的不在于如何写代码&#xff0c;用什么具体的技巧&#xff0c;编码本身只是一种将思维呈现的方式&#xff0c;但是如果思维是不清晰的&#xff0c;那么就算懂得再多的编码的奇技淫巧也是没有什么帮助的。相反&#xff0c;如果有一个清晰的…

Ubuntu20.04 Clion/Pycharm/IDEA 输入中文+光标跟随解决方案

ibus输入法&#xff08;弃用&#xff09; 之前一直用的搜狗输入法&#xff0c;但是搜狗输入法无法在Jetbrains全家桶下使用&#xff0c;但是又需要输入中文&#xff0c;没有办法我只能下载了谷歌输入法&#xff0c;十分难用&#xff0c;但是也没有其他办法&#xff0c;经常到网…

leetcode11.盛最多水的容器

题目描述 题目分析 看到题目后第一个想法当然是O(n2)O(n^2)O(n2)的&#xff0c;但是数据范围是3e4&#xff0c;应该会超时&#xff0c;而且这种数据范围也不是让暴力求解的 。 相当于求解∑i<jmax((j−i)∗min(a[i],a[j]))\sum_{i<j}{max((j-i)*min(a[i],a[j]))}∑i<…

每日一题:leetcode190.颠倒二进制位

题目描述 题目分析 题目本身很简单&#xff0c;没觉得有什么技巧可以再进行优化了&#xff0c;觉得位运算是无法打乱相对顺序的&#xff0c;而这里需要进行镜像颠倒的操作。因此就踏实地写了一个循环。 在使用位运算得到每一位的时候&#xff0c;我吸取了经验&#xff0c;用一…

结构屈曲分析

结构屈曲分析主要用于判定结构受载后是否有失稳风险&#xff0c;作为工程应用&#xff0c;一般分为线性屈曲分析和非线性屈曲分析。 线性屈曲分析需要具备较多的前提条件&#xff0c;如载荷无偏心、材料无缺陷等&#xff0c;在实际工程应用中结构制作过程和加载方式很难达到线性…

每日一题:leetcode74.搜索二维矩阵

题目描述 题目分析 感觉这是一个放错标签的简单题。题目非常简单&#xff0c;思路应该很明确是二分&#xff0c;我很快写了一个&#xff08;虽然不小心把!打成调试了一会&#xff09;。 class Solution { public:bool searchMatrix(vector<vector<int>>& mat…

每日一题:leetcode90.子集贰

题目描述 题目分析 感觉这道题让自己对枚举排列有了一个更好的认识&#xff0c;感觉自己的这种思路不错。 假设没有重复元素&#xff08;退化成78.子集&#xff09;&#xff0c;我们应该怎么做&#xff1f;初始的时候幂集中只有一个空集&#xff0c;然后对每个元素&#xff0…

每日一题:leetcode1006.笨阶乘

题目描述 题目分析 因为顺序一定且没有括号&#xff0c;所以逻辑很简单。我们要顺序处理的矛盾在于&#xff0c;减号后面会再出现乘法和除法&#xff0c;我们不妨将对乘法和除法用一个临时值进行计算&#xff0c;计算结束后再合并到值里面&#xff0c;一般来讲乘法和除法的处理…

每日一题:leetcode80.删除有序数组中的重复元素贰

题目描述 题目分析 又是一道贴错标签的简单题&#xff0c;很明显的双指针&#xff0c;我的做法是用两个变量保存是否需要记录&#xff0c;官方题解的做法是直接判断&#xff0c;人家的高明一些 class Solution { public:int removeDuplicates(vector<int>& nums) {…

每日一题:leetcode81.搜索旋转排序数组Ⅱ

题目描述 题目分析 不含重复元素的题解&#xff08;leetcode33&#xff09; 这道题也是我们算法课的一道编程题&#xff0c;写完以后发现当时的思路和现在没有什么变化&#xff0c;果然是自己啊。我的想法是先判断区间整体是升序的还是旋转的&#xff0c;如果是升序的就按照正…

C++ JSON库:JSON for Morden C++

绪论 最近因为项目的需要&#xff0c;需要对JSON进行一定的数据处理&#xff0c;因为想要用C进行编码&#xff0c;便对C的JSON库进行的调研&#xff0c;发现这个库比较好用&#xff1a;JSON for Morder C。 使用指南 想要使用这个json库&#xff0c;只需要在源文件中包含jso…

Linux信号实现精确到微秒的sleep函数:通过sigsuspend函数解决时序竞态问题

原理就是先使用定时器定时&#xff0c;然后再使用pause函数或者sigsuspend函数主动阻塞挂起&#xff0c;最终恢复现场。 如果使用pause函数的话&#xff0c;优点是使用简单&#xff0c;缺点是有可能产生时序竞态&#xff0c;导致进程一直阻塞下去&#xff1a;在定时和挂起之间…