作用:函数声明描述了函数到编译器的接口。也就是说,它将函数返回值类型以及参数的类型和数量告诉编译器。
函数声明的例子:
int sum(int a, int b);
函数声明的必须部分:
void
:表示不返回任何值auto
(C++11起):表示编译器从return语句推断类型decltype(auto)
(c++14起):类型推导返回类型函数声明的可选部分
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;
};
//Declare printf with C linkage.
extern "C" int printf( const char *fmt, ... );
inline double Account::GetBalance()
{
return balance;
}
#include
template <typename T>
T copy_object(T& obj) noexcept(std::is_pod<T>) {...}
virtual
:指定可以在派生类中重写函数overried
:表示派生类中的函数正在重写该函数finial
:表示不能在任何进一步的派生类中重写函数*this
)为右值引用与左值引用时要选择的函数的重载函数定义 = 声明 + 主体,例子:
int sum(int a, int b)
{
return a + b;
}
函数体内声明的变量称为局部变量。 它们会在函数退出时超出范围;因此,函数应永远不返回对局部变量的引用!
可以将成员函数声明为const
,作用是
// constant_member_function.cpp
class Date
{
public:
Date( int mn, int dy, int yr );
int getMonth() const; // A read-only function
void setMonth( int mn ); // A write function; can't be const
private:
int month;
};
int Date::getMonth() const
{
return month; // Doesn't modify anything
}
void Date::setMonth( int mn )
{
month = mn; // Modifies data member
}
int main()
{
Date MyDate( 7, 4, 1998 );
const Date BirthDate( 1, 18, 1953 );
MyDate.setMonth( 4 ); // Okay
BirthDate.getMonth(); // Okay
BirthDate.setMonth( 4 ); // C2662 Error
}
将函数声明为constexpr,该函数的返回值可在编译时确定。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;
}
#include
using namespace std;
void go()
{
}
void go(int a)
{
}
void go(int a, int b)
{
}
void go(int a, int b, double c = 14.0)
{
}
int main()
{
//void(*p)(int a, int b, double c = 14.0) = go; //函数指针中不能有默认参数
void(*p)(int a, int b, double c ) = go;
void(*p1)(int , int , double ) = go;
}
C++对返回值的类型有一定的限制:不能是数组,可以是其他任何对象。如果非要返回数组,请将数组作为结构会在对象组成部分来返回:
#include
struct aa{
int *a;
};
struct aa test(){
int arr[10] = {1, 2};
struct aa a{};
a.a = arr;
return a;
}
int main(){
printf("%d", test().a[0]);
}
函数是如何返回值的:函数通过将返回值复制到指定的CPU寄存器或者内存单元中来将其返回。随后,调用程序将查看该内存单元。返回函数和调用函数必须就该内存单元中存储的数据的类型达成一致。
operator、operator&、operator &&、operator *、operator ->、operator ->*、operator new、operator delete
此外,C++11标准指定以下附加规则:
这些规则的结果也可能泄露到对象层次结构中。比如,如果处于任何原因,基类无法具有可从派生类调用默认构造函数(即 public protected 不带任何参数的或构造函数),则从派生的类不能自动生成其自己的默认构造函数。
这些规则可能会使本应直接的内容、用户定义类型和常见 C++ 惯例的实现变得复杂。
在C++中,标准是通过提供了新的机制来控制默认版本函数的生成来完整合格目标的。这个新机制重用了default关键字。可以在默认函数定义或者声明时加上default,从而显式的指示编译器生成该函数的默认版本。而如果指定产生默认版后,程序不再也不应该实现一个同名的函数。此时自定义类型依旧是POD类型。
struct Two{
public:
Two() = default;
Two(int i) : data(i){}
private:
int data;
};
通过以私有方式复制构造函数和复制赋值运算符,而不定义它们,使用户定义类型不可复制。
struct noncopyable
{
noncopyable() {};
private:
noncopyable(const noncopyable&);
noncopyable& operator=(const noncopyable&);
};
在 C++11 中,不可复制的习语可通过更直接的方法实现。
struct noncopyable
{
noncopyable() =default;
noncopyable(const noncopyable&) =delete;
noncopyable& operator=(const noncopyable&) =delete;
};
具体参见:C/C++编程:noncopyable原理及实现
C++11标准将“=default”修饰的函数称为显式缺省函数。
可以默认设置任何特殊成员函数:
struct widget
{
widget()=default;
inline widget& operator=(const widget&);
};
inline widget& widget::operator=(const widget&) =default;
class ConvType{
public:
ConvType(int i){};
ConvType(char a) = delete; //删除char版本
};
void Func(int ct){}
void Func(char ct) = delete ;
int main(){
Func(3);
Func('a'); // 无法通过编译
ConvType c(3);
ConvType d('a');// 无法通过编译
}
#include
class NoHeapAlloc{
public:
void *operator new(std::size_t) = delete;
};
int main(){
NoHeapAlloc nha;
NoHeapAlloc *pb = new NoHeapAlloc;
}
#include
#include
extern void *p;
class NoStackAlloc{
public:
~NoStackAlloc() = delete;
};
int main(){
NoStackAlloc nsa; // 无法通过编译
new(p) NoStackAlloc(); // 布局new,假设p无需调用析构函数
}
由于布局new构造的对象,编译器不会为其调用析构函数,因此析构函数被删除的类能正常的构造。推导:将显式删除析构函数还可以用于构建单例模式
必须在声明函数时将其删除;不能在这之后通过声明一个函数然后不再使用的方式来将其删除。
删除普通成员函数或非成员函数可阻止有问题的类型提升导致调用意外函数。 这可发挥作用的原因是,已删除的函数仍参与重载决策,并提供比提升类型之后可能调用的函数更好的匹配。 函数调用将解析为更具体的但可删除的函数,并会导致编译器错误。
void call_with_true_double_only(float) =delete;
void call_with_true_double_only(double param) { return; }
template < typename T >
void call_with_true_double_only(T) =delete; //prevent call through type promotion of any T to double from succeeding.
void call_with_true_double_only(double param) { return; } // also define for const double, double&, etc. as needed.
C++通过与C语言相同的方式支持函数指针。但是更加类型安全的替代方法是使用函数对象
如果声明返回函数指针类型的函数,则建议使用typedef
来声明函数指针类型的别名:
typedef int (*fp)(int);
fp myFunction(char* s); // function returning function pointer
如果不执行此操作,则函数声明的正确语法可以通过用函数名称和自变量列表替换标识符(上例中为 fp)来从函数指针的声明符语法推导出,如下所示:
int (*myFunction(char* s))(int);
前面的声明等效于上面使用的声明 typedef 。
函数模板类似于类模板:它基于模板自变量生成具体功能。在虚构情况下,模板能够推断类型参数,因此无需显式的指定它们:
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
C++ 允许同一范围内具有相同名称的多个函数的规范。 这些函数称为 重载 函数。
重载函数使你能够为函数提供不同的语义,具体取决于参数的类型和数量。
函数声明元素 | 是否用于重载 |
---|---|
函数返回类型 | 否 |
自变量的数量 | 是 |
自变量的类型 | 是 |
省略号存在或者缺失 | 是 |
名称的使用typedef |
否 |
未指定的数组边界 | 否 |
const 或者volatile |
是,应用于整个函数时 |
引用限定符 | 是 |
不能为重载运算符提供默认参数
默认参数不被是为函数类型的一部分。因此,它不用于选择重载。尽在默认自变量上存在差异的两个函数被视为多个定义非不是重载函数
对于区分重载函数,类型 “数组 of” 和 “指向” 被认为是相同的,但仅适用于单个经过维度的数组。 这就是这些重载函数冲突的原因,并生成错误消息:
重载可以区分const
与voltile
引用
// argument_type_differences.cpp
// compile with: /EHsc /W3
// C4521 expected
#include
using namespace std;
class Over {
public:
Over() { cout << "Over default constructor\n"; }
Over( Over &o ) { cout << "Over&\n"; }
Over( const Over &co ) { cout << "const Over&\n"; }
Over( volatile Over &vo ) { cout << "volatile Over&\n"; }
};
int main() {
Over o1; // Calls default constructor.
Over o2( o1 ); // Calls Over( Over& ).
const Over o3; // Calls default constructor.
Over o4( o3 ); // Calls Over( const Over& ).
volatile Over o5; // Calls default constructor.
Over o6( o5 ); // Calls Over( volatile Over& ).
}
struct Object{
virtual void func() = 0;
};
struct Base : public Object{
void func() final; // 声明为final
};
struct Derived : public Base{
void func(); // 无法通过编译
};
override
,如果派生类在虚函数声明时使用了override描述符,那么该函数就必须重载其基类中的同名函数,否则代码将无法通过编译。我们来看个例子:struct Base{
virtual void Turing() = 0;
virtual void Dijkstra() = 0;
virtual void VNeumann(int g) = 0;
virtual void DKnuth() const;
void Print();
};
struct DerivedMid : public Base{
// 隔离一个接口
};
struct DeviedeTop : public DerivedMid {
void Turing() override;
void Dijkstre() override; // 无法通过编译:拼写错误,并非重载
void VNeumann(double g) override; // 无法通过编译,参数不一致,并非重载
void DKnuth() override; // 无法通过编译,常量性不一致,并非重载
void Print() override; // 无法通过编译,非虚函数重载
};
通过引用限定符,可以根据指向的对象 this 是右值还是左值,来重载成员函数。 当你选择不提供对数据的指针访问权限时,可以使用此功能来避免不必要的复制操作。 例如,假设类 C 在其构造函数中初始化某些数据,并在成员函数中返回该数据的副本 get_data() 。 如果类型为的对象 C 是要销毁的右值,则编译器将选择 get_data() && 重载,这会移动数据而不是复制数据。
#include
#include
using namespace std;
class C
{
public:
C() {/*expensive initialization*/}
vector<unsigned> get_data() &
{
cout << "lvalue\n";
return _data;
}
vector<unsigned> get_data() &&
{
cout << "rvalue\n";
return std::move(_data);
}
private:
vector<unsigned> _data;
};
int main()
{
C c;
auto v = c.get_data(); // get a copy. prints "lvalue".
auto v2 = C().get_data(); // get the original. prints "rvalue"
return 0;
}
根据参数类型委托不同的函数
比如实际上void go()–>go
void go(int a, int b)—>go_int_int
void go(int a)—>go_int函数
// function_overloading.cpp
// compile with: /EHsc
#include
#include
#include
// Prototype three print functions.
int print(std::string s); // Print a string.
int print(double dvalue); // Print a double.
int print(double dvalue, int prec); // Print a double with a
// given precision.
using namespace std;
int main(int argc, char *argv[])
{
const double d = 893094.2987;
if (argc < 2)
{
// These calls to print invoke print( char *s ).
print("This program requires one argument.");
print("The argument specifies the number of");
print("digits precision for the second number");
print("printed.");
exit(0);
}
// Invoke print( double dvalue ).
print(d);
// Invoke print( double dvalue, int prec ).
print(d, atoi(argv[1]));
}
// Print a string.
int print(string s)
{
cout << s << endl;
return cout.good();
}
// Print a double in default precision.
int print(double dvalue)
{
cout << dvalue << endl;
return cout.good();
}
// Print a double in specified precision.
// Positive numbers for precision indicate how many digits
// precision after the decimal point to show. Negative
// numbers for precision indicate where to round the number
// to the left of the decimal point.
int print(double dvalue, int prec)
{
// Use table-lookup for rounding/truncation.
static const double rgPow10[] = {
10E-7, 10E-6, 10E-5, 10E-4, 10E-3, 10E-2, 10E-1,
10E0, 10E1, 10E2, 10E3, 10E4, 10E5, 10E6 };
const int iPowZero = 6;
// If precision out of range, just print the number.
if (prec < -6 || prec > 7)
{
return print(dvalue);
}
// Scale, truncate, then rescale.
dvalue = floor(dvalue / rgPow10[iPowZero - prec]) *
rgPow10[iPowZero - prec];
cout << dvalue << endl;
return cout.good();
}
和变量一样,函数也有链接性,虽然可选择的分为比变量小。和C语言一样,C++不允许在一个函数中定义另一个函数,因此所有函数的存储持续性都自动为静态的,即在整个程序指向期间都一直存在。
默认情况下,函数的链接性为外部的,既可以在文件间共享。
实际上,可以在函数原型中使用extern来指出函数是在另一个文件中定义的。
还可以使用static将函数的链接性设置为内部的,使之只能在一个文件中使用。这还意味着可以在其他文件中定义同名的函数。和变量一样,在定义静态函数的文件中,静态函数将覆盖外部定义,因此即使在外部定义了同名的函数,该文件仍将使用静态函数。
单定义规则也适用于非内联函数,因此,对于每个非内联函数,程序中只能包含一个定义。而内联函数不受这个规则约束,这允许程序员能够将内联函数定义放在头文件中。这样,包含了头文件的每个文件都有内联函数定义,然而,C++要求同一个函数的所有内联定义都必须相同。