编程语言规定规则,编译器实现预定规则。
程序员按预定规则编制源程序,编译器、链接器按预定规则编译源程序、链接相关部分为可执行文件,计算机平台加载并运行可执行文件。
编程语言的抽象层次越高,规则背后隐藏的细节越多,背后编译器完成的工作越多。
函数是任何编程语言的最基本和最重要的构件。C++的函数类别更是纷繁复杂:
1 C++11之前
1.1 非成员函数或自由函数
1.2 成员函数
1.3 可变参函数
1.4 重载函数
1.5 静态函数
1.6 内联函数
1.7 运算符重载函数
1.8 只读成员函数
1.9 友元函数
1.10 虚成员函数
1.11 特殊成员函数(构造、析构、拷贝、拷贝构造)
1.12 函数模板、类模板、成员模板
2 C++11
2.1 可变参模板函数
2.2 返回值类型后置模板函数
2.3 带override 说明符成员函数
2.4 带final说明符成员函数
2.5 增加的特殊成员函数(移动、移动赋值)
2.6 default 成员函数(显式预置的函数定义)
2.7 delete 成员函数(显式弃置的函数定义)
2.8 delete 其他函数
2.9 lambda 函数
3 C++14
3.1 返回类型自动推导函数
3.2 泛型lambda
4 C++17
4.1 扩展 lambda
4.2 向 lambda 传递 this 的拷贝
4.3 异常声明作为函数类型的一部分
5 C++20
5.1 consteval 函数(立即函数)
5.2 简短的函数模板
5.3 lambda 函数模板
5.4 constexpr 虚成员函数
5.5 协程
6 未来
6.1 Contracts 合约
1 C++11之前
1.1 非成员函数或自由函数
int add(int a, int b)
{
return a + b;
}
函数(自由函数)是编程语言构建程序模块的根本,一切其它类型的函数都会被处理为自由函数。
1.2 成员函数
它们是类/结构的一部分。这些也被称为方法(就像在大多数其他面向对象的编程语言中一样),尽管这个术语在C++标准中没有被使用。下面是一个例子:
class Calc
{
int a,int b;
public:
Calc(int a,int b):a(a),b(b){}
int add();
};
int Calc::add()
{
return this->a + this->b;
}
成员函数最终会处理成自由函数,编译器背后的行为有:
① 添加指向对象的this指针做函数参数;
② 添加类名命名空间;
1.3 可变参函数
include
include
int sum(int count, ...); //原型中使用省略号
int sum(int count, ...){
va_list ap; // typedef char * va_list;
va_start(ap,count); // 确定第一个参数后参数的内存位置
int sum = 0;
for(int i = 0; i < count; i++)
sum += va_arg(ap, int); // 根据可变参数类型及前一个参数的位置来确定指针偏移并做解引用
va_end(ap);
return sum;
}
main(){
printf("%d",sum(3,11,22,33)); // 66
getchar();
}
可变参函数的实现思路:
根据函数调用约定和第一个参数压入栈的位置,及第一个参数提供的信息,来确定其它参数在栈帧上的位置,其实现通过一个字节指针来实现。第一个参数可以是一个确定可变参数数量的整型变量,也可以是一个格式化字符串常量。
以下是实现可变参数函数的5个宏:
//代码效果参考:http://www.zidongmutanji.com/zsjx/421117.html
define _VA_LIST_DEFINED
define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) // 考虑了内存对齐
define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
define va_arg(ap,t) ( (t )((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
define va_end(ap) ( ap = (va_list)0 )
1.4 重载函数
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
重载函数无它,只是名称修饰(name mangling)而已。
1.5 静态函数
include
class Account{
double balance;
public:
static double interest;
Account(int bal):balance(bal){}
static void setInterest(double i)
{
interest = i;
}
double getBalance(){
return balance;
}
};
double Account::interest = 0.3;
main(){
Account ac(3000);
ac.setInterest(0.4);
printf("%lf\n",ac.getBalance()*(1+Account::interest));//4200
getchar();
}
对于结构化编程来说,要尽量避免使用全局变量,面向对象编程也是如此。
对于类来说,有些类的数据独立于对象而存在(类共享),自然,这类数据无须类实例化即可使用,这类数据称为类的静态数据成员(需要在类外声明并初始化),用static修饰,静态数据成员通过一类没有隐含的this指针的成员函数来处理,这类成员函数就是静态成员函数(不能处理非静态数据成员,因为没有隐含的this指针)。
1.6 内联函数
inline int add(int a, int b) {return a + b;}
struct math
{
inline int add(int a, int b);
}
int match::add(int a, int b) {return a + b;}
函数实现了代码的模块化,但函数的调用是一个较复杂的过程,其开销(overhead)总是存在的。在C中,折衷的做法是使用参数宏来代替短小简单的函数,但参数宏是没有数据类型使用的,自然也谈不上数据类型检查。C++的做法是使用用inline修饰的内联函数,既有类型检查,也能在编译前在内联函数调用处做代码展开,避免函数调用开销。
1.7 运算符重载函数
include
include
std::string operator+(std::string const & txt, int n)
{
return txt + std::to_string(n); //C++11
}
int main(){
const std::string s = "123";
int n = 456;
std::string str =s+n; // operator+(s,n);
std::cout<<str<<std::endl;
getchar();
return 0;
}
在C++中,运算符可以理解为一类特殊的函数,函数名为关键字operator+运算符。
1.8 只读成员函数
class wrapper
{
public:
wrapper(int a): value(a) {}
int get() const {return value;}
private:
int value_;
};
只读成员函数表示其只读类的数据成员。
1.9 友元函数
class math
{
int a,b;
public:
math(int a,int b):a(a),b(b){}
friend int add(math m);
};
int add(math m)
{
return m.a + m.b;
}
友元函数是一种特殊的成员函数,能获得访问访问类的private数据成员的权限。
1.10 虚成员函数
struct A
{
virtual void f() { std::cout << "A::f()\n"; }
};
struct B : public A
{
virtual void f() { std::cout << "B::f()\n"; }
};
虚成员函数用于继承层次中函数重写,实现同一接口适用继承层次中的不同对象,呈现不同行为。编译器背后的行为有:
① 为继承层次中的每个类建立一个虚函数表;
② 在基类中插入一个指向虚函数表的指针。
③ 虚函数调用时会根据其所在类指向虚函数表的指针来动态匹配并调用虚函数。
1.11 特殊成员函数(构造、析构、拷贝、拷贝复制)
class wrapper
{
public:
wrapper() : value(0) {} // 构造函数,用于数据成员初始化
wrapper(wrapper const & other) { // 拷贝构造,使用另一个对象的状态来初始化数据成员
value = other.value; }
wrapper& operator=(wrapper const & other) { // 拷贝函数,,使用另一个对象的状态来更新数据成员
if(this != &other) {
value = other.value;} }
~wrapper() {} // 析构函数,需要时,释放资源资源,如堆内存
private:
int value;
};
抽象数据类型通过特殊成员函数实现RAII(Resource Acquisition Is Initialization)。
1.12 函数模板、类模板、成员模板
template
T add(T a, T b)
{
return a + b;
}
template
class math1
{
public:
T add(T a, T b)
{
return a + b;
}
};
class math2
{
public:
template
T add(T a, T b)
{
return a + b;
}
};
模板是类型的参数化,模板的具体化由编译器完成。
//代码效果参考:http://www.zidongmutanji.com/bxxx/553946.html
2 C++11
2.1 可变参模板函数
template
T add(T a, T b)
{
return a + b;
}
template
T add(T t, Ts ... rest)
{
return t + add(rest...);
}
可变参模板函数是可变参函数与模板函数两种语法机制的结合。
2.2 返回值类型后置模板函数
auto add(int a, int b) -> int
{
return a + b;
}
template
auto add(T const & a, U const & b) -> decltype(a + b)
{
return a + b;
}
constexpr 函数
template
constexpr T add(T a, T b)
{
return a + b;
}
int main()
{
int arr[add(1,2)] = {1,2,3}; // [1]
int a, b;
std::cin >> a >> b;
std::cout << add(a, b) << '\n'; // [2]
}
其实质也是一种数据类型声明和数据类型推断。
2.3 带override 说明符成员函数
struct A
{
virtual void f(int) {}
virtual void g() {}
};
struct B : public A
{
void f(int) override {} // OK
void g(char) override {} // error, g() does not override anything
};
显示声明虚函数重写。
2.4 带final说明符成员函数
struct A
{
virtual void f() {}
};
struct B : public A
{
void f() override (final {}
};
struct C : public B
{
void f() override {} // error, f cannot be overridden anymore
};
显式声明虚函数不要被重写。
2.5 增加的特殊成员函数(移动构造、移动赋值)
struct buffer
{
buffer() // default constructor
:data(nullptr), size(0)
{}
explicit buffer(sizet size) // constructor
:data(new char[size]), size_(size)
{}
~buffer() // destructor
{
delete [] data_;
}
buffer(buffer const & other) // copy constructor
: data(new char[other.size])
, size(other.size)
{
std::memcpy(data, other.data, size_);
}
buffer& operator=(buffer const & other) // copy assignment operator
{
if(this != &other)
{
delete [] data;
data = new char[other.size];
size = other.size;
std::memcpy(data, other.data, size);
}
return *this;
}
buffer(buffer&& other) // move constructor
: data(std::move(other.data))
, size(other.size)
{
other.data = nullptr;
other.size = 0;
}
buffer& operator=(buffer&& other) // move assignment operator
{
if(this != &other)
{
delete [] data;
data = std::move(other.data);
size = other.size;
other.data = nullptr;
other.size_ = 0;
}
return *this;
}
//代码效果参考:http://www.zidongmutanji.com/bxxx/243158.html
private:
char* data_;
sizet size;
};
int main()
{
buffer b1;
buffer b2(10);
buffer b3 = b2;
buffer b4 = std::move(b3);
}移动构造、移动赋值
移动构造、移动赋值成员函数对于临时对象,使用移动而不是复制,具有更高的效率。
2.6 default 成员函数(显式预置的函数定义)
struct foo
{
foo(int) {} // user-defined constructor
foo() = default; // compiler generated default constructor
};
显式要求编译器生成一个默认构造函数。
2.7 delete 成员函数(显式弃置的函数定义)
struct noncopyable
{
noncopyable() = default;
noncopyable(noncopyable const &) = delete;
noncopyable& operator=(noncopyable const &) = delete;
};
delete显式要求编译器不要生成对应类型的成员函数。
2.8 delete 其他函数
template
T add(T a, T b)
{
return a + b;
}
template <>
int add(int a, int b) = delete;
int main()
{
add(1, 2); // error, this specialization is deleted
}
显式要求编译器不要生成特定参数类型的模板函数。
2.9 lambda 函数
int main()
{
auto add = { return a + b; };
add(1, 2);
}
lambda函数可以理解为函数对象的语法糖。
3 C++14
3.1 返回类型自动推导函数
auto add(int a, int b)
{
return a + b;
}
template
auto add(T a, U b)
{
return a + b;
}
3.2 泛型lambda
int main()
{
using namespace std::string_literals;
auto add = [](auto a, auto b) {return a + b;};
add(1, 2);
add(1.0, 2.0);
add("1"s, "2"s);
}
4 C++17
4.1 扩展 lambda
constexpr lambda,自从 C++17 起,lambda表达式会尽可能的隐式声明 constexpr。
auto squared = { // 自 从C++17起 隐 式constexpr
return valval;
};
std::array a; // 自 从C++17起OK => std::array
4.2向 lambda 传递 this 的拷贝
class C {
private:
std::string name;
public:
void foo() {
auto l1 = [this] { std::cout << name << '\n'; };
}
};
4.3 异常声明作为函数类型的一部分
这里,派生类中的成员函数 foo() 和基类中的 foo() 类型不同所以不能重载。这段代码不能通过编译,即使 没有 override 修饰符代码也不能编译,因为我们不能用更宽松的异常声明重载。
class Base {
public:
virtual void foo() noexcept;
};
class Derived : public Base {
public:
void foo() override; // ERROR: 不 能 重 载
//...
};
5 C++20
5.1 consteval 函数(立即函数)
consteval int add(int const a, int const b)
{
return a + b;
}
int main()
{
constexpr int s1 = add(1, 2); // OK, compile-time evaluation
int a = 12, b = 66;
const int s2 = add(a, b); // error
using fptr = int(int, int);
fptr* padd = add; // error
}
5.2 简短的函数模板
auto add(auto a, auto b)
{
return a + b;
}
5.3 lambda 函数模板
auto add = {return a + b;};
5.4 constexpr 虚成员函数
struct magic
{
constexpr virtual int def() const { return 0; }
};
struct programming_magic : public magic
{
constexpr int def() const (override { return 42; }
};
constexpr int initval(magic const & m)
{
return m.def() + 1;
}
int main()
{
constexpr programming_magic pm;
int arr[initval(pm)] = {0};
}
5.5 协程
协程,这个是C++20标准的主要特征之一。coroutine是一个具有暂停和恢复能力的函数。不幸的是,C++20只定义了一个执行程序的框架,但并没有定义任何满足这种要求的程序类型。这意味着,我们需要自己编写或者依靠第三方库来实现。这样一个库就是cppcoro库。在C++20中,有三个新的关键字,用于coroutine:co_await,co_return,和co_yield。如果一个函数使用了这三个中的一个,它就会成为一个循环程序。
co_await操作符,用于暂停执行,直到重新开始。
co_return关键字,完成执行并可选返回一个值
co_yield关键字用于暂停执行并返回一个值
include
cppcoro::generator produce_items()
{
while (true)
{
auto v = rand();
using namespace std::string_literals;
auto i = "item "s + std::to_string(v);
print_time();
std::cout << "produced " << i << '\n';
co_yield i;
}
}
include
cppcoro::task<> consume_items(int const n)
{
int i = 1;
for(auto const& s : produce_items())
{
print_time();
std::cout << "consumed " << s << '\n';
if (++i > n) break;
}
co_return;
}