C++中函数使用的基本知识学习教程

函数是执行某种操作的代码块。函数可以选择性地定义使调用方可以将实参传递到函数中的输入形参。函数可以选择性地返回值作为输出。函数可用于在单个可重用块中封装常用操作(理想情况是使用可清晰地描述函数行为的名称)。以下函数从调用方接受两个整数并返回其总和;a 和 b 是 int 类型的参数。

int sum(int a, int b)
{
  return a + b;
}

可以从程序中任意数量的位置调用函数。传递给函数的值是实参,其类型必须与函数定义中的形参类型兼容。

int main()
{
  int i = sum(10, 32);
  int j = sum(i, 66);
  cout << "The value of j is" << j << endl; // 108
}

对于函数长度没有实际限制,不过良好的设计应以执行单个明确定义的任务的函数为目标。复杂算法应尽可能分解成易于理解的更简单函数。
在类范围中定义的函数称为成员函数。在 C++ 中(与其他语言不同),函数还可以在命名空间范围中定义(包括隐式全局命名空间)。这类函数称为 free 函数或非成员函数;它们在标准库中广泛使用。
函数声明的各个部分
最小函数声明包含返回类型、函数名和参数列表(可能为空),以及向编译器提供附加说明的可选关键字。函数定义包含声明以及函数体(这是大括号之间的所有代码)。后接分号的函数声明可以出现在程序中的多个位置处。它必须在每个翻译单元中对该函数的任何调用之前出现。根据单个定义规则 (ODR),函数定义必须仅在程序中出现一次。
函数声明的必需部分有:
返回类型,指定函数将返回的值的类型,如果不返回任何值,则为 void。在 C++11 中,auto 是有效返回类型,可指示编译器从返回语句推断类型。在 C++14 中,还允许使用 decltype(auto)。有关详细信息,请参阅下面的“返回类型中的类型推导”。
函数名,必须以字母或下划线开头,不能包含空格。一般而言,标准库函数名中的前导下划线指示私有成员函数,或不是供你的代码使用的非成员函数。
参数列表(一组用大括号限定、逗号分隔的零个或多个参数),指定类型以及可以用于在函数体内访问值的可选局部变量名。
函数声明的可选部分有:
constexpr,指示函数的返回值是常量值,可以在编译时进行计算。

       constexpr float exp(float x, int n)
{
  return n == 0 ? 1 :
    n % 2 == 0 ? exp(x * x, n / 2) :
    exp(x * x, (n - 1) / 2) * x;
};

其 linkage 规范(extern 或 static)。

Declare printf with C linkage.
extern "C" int printf( const char *fmt, ... );

inline,指示编译器将对函数的每个调用替换为函数代码本身。在某个函数快速执行并且在性能关键代码段中重复调用的情况下,内联可以帮助提高性能。

inline double Account::GetBalance()
{
  return balance;
}

noexcept,指定函数是否可以引发异常。在下面的示例中,函数在 is_pod 表达式计算结果为 true 时不引发异常。

#include <type_traits>

template <typename T>
T copy_object(T& obj) noexcept(std::is_pod<T>) {...}

仅限成员函数)cv 限定符,指定函数是 const 还是 volatile。
(仅限成员函数)virtual、override 或 final。 virtual 指定可以在派生类中重写函数。 override 表示派生类中的函数在重写虚函数。 final 表示函数不能在任何进一步的派生类中进行重写。

(仅限成员函数仅)应用于成员函数的 static 表示函数不与类的任何对象实例关联。
(仅限非静态成员函数)ref 限定符,向编译器指定隐式对象参数 (*this) 是右值引用与左值引用时要选择的函数重载。
下图显示了函数定义的各个部分。灰色区域是函数体。

函数定义部分
函数定义
函数体内声明的变量称为局部变量。它们会在函数退出时超出范围;因此,函数应永远不返回对局部变量的引用!
函数模板
函数模板类似于类模板;它基于模板参数生成具体功能。在许多情况下,模板能够推断类型参数,因此无需显式指定它们。

template<typename Lhs, typename Rhs>
auto Add2(const Lhs& lhs, const Rhs& rhs)
{
  return lhs + rhs;
}

auto a = Add2(3.13, 2.895); // a is a double
auto b = Add2(string{ "Hello" }, string{ " World" }); // b is a std::string
有关详细信息,请参阅函数模板

函数形参和实参
函数具有零种或多种类型的逗号分隔参数列表,其中每个参数都具有可以用于在函数体内访问它的名称。函数模板可以指定其他类型或值参数。调用方传递实参(其类型与形参列表兼容的具体值)。
默认情况下,参数通过值传递给函数,这意味着函数会收到所传递的对象的副本。对于大型对象,创建副本可能成本高昂,并非始终必要。若要使实参通过引用(特别是左值引用)进行传递,请向形参添加引用限定符:

void DoSomething(std::string& input){...}

当函数修改通过引用传递的参数时,它会修改原始对象,而不是本地副本。若要防止函数修改这类实参,请将形参限定为

const&:
void DoSomething(const std::string& input){...}

C++ 11:若要显式处理通过右值引用或通过左值引用传递的实参,请对形参使用双与号以指示通用引用:

void DoSomething(const std::string&& input){...}

只要关键字 void 是实参声明列表中的第一个也是唯一一个成员,那么在形参声明列表中使用单个关键字 void 声明的函数就没有实参。列表中的其他地方的 void 类型的参数产生错误。例如:

// OK same as GetTickCount()
long GetTickCount( void );

请注意,尽管指定 void 参数是非法(此处所述的除外),但派生自类型 void 的类型(如指向 void 的指针和 void 的数组)可以出现在参数声明列表中的任何位置。
默认参数
函数签名中的最后一个或几个形参可能会分配有默认实参,这意味着调用方可能会在调用函数时省略实参(除非要指定某个其他值)。

int DoSomething(int num,
  string str,
  Allocator& alloc = defaultAllocator)
{ ... }

// OK both parameters are at end
int DoSomethingElse(int num,
  string str = string{ "Working" },
  Allocator& alloc = defaultAllocator)
{ ... }

// C2548: 'DoMore': missing default parameter for parameter 2
int DoMore(int num = 5, // Not a trailing parameter!
  string str,
  Allocator& = defaultAllocator)
{...}

函数返回类型
函数可能不会返回另一个函数或内置数组;但是,它可以返回指向这些类型的指针,或生成函数对象的 lambda。除了这些情况,函数可以返回处于范围内的任何类型的值,或者它可以返回任何值(在这种情况下返回类型是 void)。
结尾返回类型
“普通”返回类型位于函数签名左侧。结尾返回类型位于签名的最右侧,前面带有 -> 运算符。当返回值的类型取决于模板参数时,结尾返回类型在函数模板中尤其有用。

template<typename Lhs, typename Rhs>
auto Add(const Lhs& lhs, const Rhs& rhs) -> decltype(lhs + rhs)
{
  return lhs + rhs;
}

当 auto 与结尾返回类型结合使用时,它对于 decltype 表达式生成的任何内容都只用作占位符,本身不执行类型推导。
返回类型中的类型推导 (C++14)
在 C++14 中,可以使用 auto 指示编译器从函数体推断返回类型,而不必提供结尾返回类型。请注意,auto 始终推导为按值返回。使用 auto&& 可指示编译器推导引用。
在此示例中,auto 会推导为 lhs 和 rhs 之和的非常量值副本。

template<typename Lhs, typename Rhs>
auto Add2(const Lhs& lhs, const Rhs& rhs)
{
  return lhs + rhs; //returns a non-const object by value
}

请注意,auto 不会保留它推导的类型的常量性。对于返回值需要保留其参数的常量性或引用性的转发函数,可以使用 decltype(auto) 关键字,该关键字使用 decltype 类型推断规则并保留所有类型信息。 decltype(auto) 可以用作左侧的普通返回值,或结尾返回值。
下面的示例(基于来自 N3493 的代码)演示的 decltype(auto) 用于采用在模板实例化之前未知的返回类型实现函数参数的完美转发。

template<typename F, typename Tuple = tuple<T...>, int... I>
decltype(auto) apply_(F&& f, Tuple&& args, index_sequence<I...>)
{
  return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...);
}

template<typename F, typename Tuple = tuple<T...>,
  typename Indices = make_index_sequence<tuple_size<Tuple>::value >>
  decltype( auto)
  apply(F&& f, Tuple&& args)
{
  return apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices());
}
}

函数局部变量
在函数主体内声明的变量称为局部变量。非静态局部变量仅在函数体中可见,如果它们在堆栈上声明,则会在函数退出时超出范围。构造局部变量并通过值返回它时,编译器通常可以执行返回值优化以避免不必要的复制操作。如果通过引用返回局部变量,则编译器会发出警告,因为调用方为使用该引用而进行的任何尝试会在局部变量已销毁之后进行。
局部静态对象将在 atexit 指定的终止期间销毁。如果某个静态对象由于程序的控制流跳过了其声明而未构造,则不会尝试销毁该对象。
静态局部变量
在 C++ 中,局部变量可以声明为静态。变量仅在函数体中可见,但是对于函数的所有实例,存在变量的单个副本。
函数指针
C++ 通过与 C 语言相同的方式支持函数指针。但是更加类型安全的替代方法通常是使用函数对象。
建议使用 typedef 声明函数指针类型的别名(如果声明返回函数指针类型的函数)。例如

typedef int (*fp)(int);
fp myFunction(char* s); // function returning function pointer

如果不执行此操作,则函数声明的正确语法可以通过用函数名称和参数列表替换标识符(上例中为 fp)来从函数指针的声明符语法推导出,如下所示:

int (*myFunction(char* s))(int);

前面的声明与使用上面的 typedef 的声明等效。

(0)

相关推荐

  • 详解在C++中显式默认设置的函数和已删除的函数的方法

    在 C++11 中,默认函数和已删除函数使你可以显式控制是否自动生成特殊成员函数.已删除的函数还可为您提供简单语言,以防止所有类型的函数(特殊成员函数和普通成员函数以及非成员函数)的参数中出现有问题的类型提升,这会导致意外的函数调用. 显式默认设置的函数和已删除函数的好处 在 C++ 中,如果某个类型未声明它本身,则编译器将自动为该类型生成默认构造函数.复制构造函数.复制赋值运算符和析构函数.这些函数称为特殊成员函数,它们使 C++ 中的简单用户定义类型的行为如同 C 中的结构.也就是说,可以创

  • 实例讲解在C++的函数中变量参数及默认参数的使用

    包含变量参数列表的函数 如果函数声明中最后一个成员是省略号 (...),则函数声明可采用数量可变的参数.在这些情况下,C++ 只为显式声明的参数提供类型检查.即使参数的数量和类型是可变的,在需要使函数泛化时也可使用变量参数列表.函数的系列是一个使用变量参数列表的函数的示例.printfargument-declaration-list 包含变量参数的函数 若要访问声明后的参数,请使用包含在标准包含文件 STDARG.H 中的宏(如下所述). 采用数量可变的参数的函数声明至少需要一个占位符参数(即

  • 深度探究C++中的函数重载的用法

    C++ 允许同一范围内具有相同名称的多个函数的规范.这些函数称为重载函数,"重载"中对其进行了详细介绍.利用重载函数,程序员可以根据参数的类型和数量为函数提供不同的语义. 例如,采用字符串(或 char *)参数的 print 函数执行的任务与采用"双精度"类型的参数的函数执行的任务截然不同.重载允许通用命名并使程序员无需创建名称,例如 print_sz 或 print_d.下表显示了 C++ 使用函数声明的哪些部分来区分同一范围内具有相同名称的函数组. 重载注意事

  • 解析C++中多层派生时的构造函数及一些特殊形式

    C++多层派生时的构造函数 一个类不仅可以派生出一个派生类,派生类还可以继续派生,形成派生的层次结构.在上面叙述的基础上,不难写出在多级派生情况下派生类的构造函数. 通过例下面的程序,读者可以了解在多级派生情况下怎样定义派生类的构造函数.相信大家完全可以自己看懂这个程序. [例] 多级派生情况下派生类的构造函数. #include <iostream> #include<string> using namespace std; class Student//声明基类 { publi

  • 深入解析C++中派生类的构造函数

    基类的构造函数不能被继承,在声明派生类时,对继承过来的成员变量的初始化工作也要由派生类的构造函数来完成.所以在设计派生类的构造函数时,不仅要考虑派生类新增的成员变量,还要考虑基类的成员变量,要让它们都被初始化. 解决这个问题的思路是:在执行派生类的构造函数时,调用基类的构造函数. 下面的例子展示了如何在派生类的构造函数中调用基类的构造函数. #include<iostream> using namespace std; //基类 class People{ protected: char *n

  • C++中replace()函数使用方法汇总

    C++编程语言中的string应用方式多样化,每一种应用方式都能帮助我们提实现特定的功能需求.在这里我们将会为大家详细介绍一下其中一个比较重要的用法,有关C++ replace()函数的应用方式. basic_string::max_size C++ replace()函数返回string 能放的最大元素个数.(不同于capacity) size _ type max _ size( ) const; basic_string <char>::size_type cap, max; cap =

  • VC++ 使用 _access函数判断文件或文件夹是否存在

    _access函数 int _access( const char *path, int mode ); int _waccess( const wchar_t *path, int mode ); 参数 path 文件或目录路径. mode 读/写特性. 返回值   如果文件包含特定模式,每个函数返回 0. 函数返回 - 1,则名称文件不存在或不具有特定模式:在这种情况下,如下表errno 所示设置. EACCES 访问被拒绝:文件权限的设置不允许指定的访问权限. ENOENT 未找到文件名或

  • C++编程中队内联函数的理解和使用

    函数调用过程 c++经过编译生成可执行程序文件exe,存放在外存储器中.程序启动,系统从外存储器中将可执行文件装载到内存中,从入口地址(main函数起始处)开始执行.程序执行中遇到了对其他函数的调用,就暂停当前函数的执行,并保存下一条指令的地址作为从被调函数返回后继续执行的入口点,保存现场.然后转到被调函数的入口地址执行被调函数.遇到return语句或者被调函数结束后,恢复先前保存的现场,从先前保存的返回地址处继续执行主调函数的其余部分. 内联函数 函数调用需要进行现场保护,以便在函数调用之后继

  • C++中strcpy函数的实现

    我们先来看个例子 char * strcpy(char * strDest,const char * strSrc) { if ((NULL==strDest) || (NULL==strSrc)) throw "Invalid argument(s)"; char * strDestCopy = strDest; while ((*strDestCopy++ = *strSrc++) != '\0'); return strDest; } 突然想到之前做过的一个试题 题目:    

  • C++中函数使用的基本知识学习教程

    函数是执行某种操作的代码块.函数可以选择性地定义使调用方可以将实参传递到函数中的输入形参.函数可以选择性地返回值作为输出.函数可用于在单个可重用块中封装常用操作(理想情况是使用可清晰地描述函数行为的名称).以下函数从调用方接受两个整数并返回其总和:a 和 b 是 int 类型的参数. int sum(int a, int b) { return a + b; } 可以从程序中任意数量的位置调用函数.传递给函数的值是实参,其类型必须与函数定义中的形参类型兼容. int main() { int i

  • Python中的字符串类型基本知识学习教程

    如果对自然语言分类,有很多中分法,比如英语.法语.汉语等,这种分法是最常见的.在语言学里面,也有对语言的分类方法,比如什么什么语系之类的.我这里提出一种分法,这种分法尚未得到广大人民群众和研究者的广泛认同,但是,我相信那句"真理是掌握在少数人的手里",至少在这里可以用来给自己壮壮胆. 我的分法:一种是语言中的两个元素(比如两个字)拼接在一起,出来一个新的元素(比如新的字):另外一种是两个元素拼接在一起,只是得到这两个元素的并列显示.比如"好"和"人&quo

  • Java中的数组基础知识学习教程

    数字 通常情况下,当我们处理数字时,使用原始数据类型,如 byte,int,long,double 等. 示例 int i = 5000; float gpa = 13.65; byte mask = 0xaf; 然而,在开发中,我们会遇到需要使用对象而不是原始数据类型的情况.为了实现这个, Java 为每个原始数据类型提供包装类. 所有的包装类 (Integer, Long, Byte, Double, Float, Short) 是抽象类 Number 的子类. 这种包装是由编译器处理,这个

  • C++中变量的类型与作用域学习教程

    C++ 变量类型 变量其实只不过是程序可操作的存储区的名称.C++ 中每个变量都有指定的类型,类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可应用于变量上. 变量的名称可以由字母.数字和下划线字符组成.它必须以字母或下划线开头.大写字母和小写字母是不同的,因为 C++ 是大小写敏感的. 基于前一章讲解的基本类型,有以下几种基本的变量类型,将在下面进行讲解: 类型 描述 bool 存储值 true 或 false. char 通常是一个八位字节(一个字节).这是一个整数类型

  • Python中的条件判断语句基础学习教程

    if语句用来检验一个条件, 如果 条件为真,我们运行一块语句(称为 if-块 ), 否则 我们处理另外一块语句(称为 else-块 ). else 从句是可选的. 使用if语句: #!/usr/bin/python # Filename: if.py number = 23 guess = int(raw_input('Enter an integer : ')) if guess == number: print 'Congratulations, you guessed it.' # New

  • C#中的delegate委托类型基本学习教程

    委托 delegate 是表示对具有特定参数列表和返回类型的方法的引用的类型.在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联.你可以通过委托实例调用方法. 委托用于将方法作为参数传递给其他方法.事件处理程序就是通过委托调用的方法.你可以创建一个自定义方法,当发生特定事件时,某个类(如 Windows 控件)就可以调用你的方法.下面的示例演示了一个委托声明: public delegate int PerformCalculation(int x, int y); 可将任何

  • C语言中的结构体的入门学习教程

    C语言中数组允许定义类型的变量,可容纳相同类型的多个数据项,但结构体在C语言编程中,它允许定义不同种类的数据项可供其他用户定义的数据类型. 结构是用来代表一个记录,假设要跟踪图书馆的书籍.可能要跟踪有关每本书以下属性: Title - 标题 Author - 作者 Subject - 科目 Book ID - 编号 定义结构体 定义一个结构体,必须使用结构体的struct语句.该struct语句定义了一个新的数据类型,程序不止一个成员.struct语句的格式是这样的: struct [struc

  • MySQL中LIKE子句相关使用的学习教程

    MySQL LIKE 语法 LIKE 运算符用于 WHERE 表达式中,以搜索匹配字段中的指定内容,语法如下: WHERE column LIKE pattern WHERE column NOT LIKE pattern 在 LIKE 前面加上 NOT 运算符时,表示与 LIKE 相反的意思,即选择 column 不包含 pattern 的数据记录. LIKE 通常与通配符 % 一起使用,% 表示通配 pattern 中未出现的内容.而不加通配符 % 的 LIKE 语法,表示精确匹配,其实际效

  • Python中的列表生成式与生成器学习教程

    列表生成式 即创建列表的方式,最笨的方法就是写循环逐个生成,前面也介绍过可以使用range()函数来生成,不过只能生成线性列表,下面看看更为高级的生成方式: >>> [x * x for x in range(1, 11)] [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] 写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法. 你甚至可以在后面加上if判断: >>

  • MySQL中的唯一索引的简单学习教程

    mysql 唯一索引UNIQUE一般用于不重复数据字段了我们经常会在数据表中的id设置为唯一索引UNIQUE,下面我来介绍如何在mysql中使用唯一索引UNIQUE吧. 创建唯一索引的目的不是为了提高访问速度,而只是为了避免数据出现重复.唯一索引可以有多个但索引列的值必须唯一,索引列的值允许有空值.如果能确定某个数据列将只包含彼此各不相同的值,在为这个数据列创建索引的时候就应该使用关键字UNIQUE. 把它定义为一个唯一索引. 创建表时直接设置: DROP TABLE IF EXISTS `st

随机推荐