void go()
{
static int i = 1;
++i;
}
int main() {
go();
go();
return 0;
}
当程序执行到第2行 '{' 时,变量i初始化为1,第一个go()调用后i为2,进入第二个go()调用时,i = 2。
函数的声明不带函数体,即将大括号换成分号:
void fun(int, int);
在函数声明中参数的名字是不必要的。函数声明包括三部分:
返回值,
函数名,
参数类型。这三部分描述了函数接口。函数声明也称为
函数原型。
函数调用时的参数传递有两种方式:1. 传值; 2. 传引用。对于占用内存很大的对象,传递引用可以避免大量的空间开销。
void go(int &i)
{
}
int main() {
int i = 1;
const int &ri = i;
go(ri); // error
go(10); // error
return 0;
}
加上const
void go(const int &i)
{
}
int main() {
int i = 1;
const int &ri = i;
go(ri); // ok
go(10); // ok
return 0;
}
int a[10];
int *p = a;
cout << a[5] << endl; // 等价于 *(p+5)
在传递数组时,实际上传递的是指向数组的指针。
void go(int *pa);
void go(int a[]); // 强调传递的是数组
void go(int a[10]); //
...
int a0 = 0;
int a1[2] = {0,1};
int a2[15] = {0,1};
go(&a0); // ok
go(a1); // ok
go(a2); // ok
这三种定义方法在某种程度上是等价的,因为在调用go()时,编译器只会检查实参的类型是否是int,即检查数组元素的类型。所以上面的调用都是合法的,但是数组范围的可控制则需要很小心。看下面的示例:
void go(int a[10])
{
for (int i=0; i<10; ++i)
cout << a[i] << endl;
}
int main() {
int a1[2] = {0,1};
int a2[15] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
go(a1);
go(a2);
return 0;
}
go(a1)调用之后,输出的十个数,后面八个是未定义的,因为数组a1只定义了两个元素;
void go(int a[10])
{
for (int i=0; i<15; ++i)
cout << a[i] << endl;
}
go(a2)调用后,输出a2的15个元素。你可能会以为后五个元素是未定义的,因为形参是
int a[10]。但是,前面已经指出,它和
int *pa的定义方法等价,因为数组就是指针。
void go(int *beg, int *end)
{
while (beg != end) {
cout << *beg++ << endl;
}
}
我们知道end指向的是数组的超出末端位置,不能对其引用。int a[10] = {};
go(begin(a), end(a));
void go(int (&ra)[10]); //(&ra)的括号不能省
因为数组的大小是其类型的一部分,所以调用go时传递的实参也必须严格的是int [10],显然这会限制其使用。
void goMul(int (*paa)[5], int crow)
{
for (int row=0; row < crow; ++row) {
for (int col=0; col < 5; ++col)
cout << paa[row][col] << " ";
cout << endl;
}
}
int main() {
int aa[2][5] = {
1, 2, 3, 4, 5,
6, 7, 8, 9, 10
};
goMul(aa, 2);
return 0;
}
initializer_list |
默认初始化,元素类型为T的空列表 |
initializer_list |
lst的元素个数可以和initializers一样多;元素是对于的 initializers的副本。列表中的元素是const的 |
lst2(lst) lst2 = lst |
对initializer_list拷贝或赋值不会复制列表中的元素。复制后 副本和原列表共享元素 |
lst.size() | 列表中的元素数 |
lst.begin() lst.end() |
返回lst中第一个元素和过一个最后元素的指针。 |
int &swapXY(int &x, int &y)
{
int temp;
temp = x;
x = y;
y = temp;
return x;
}
int main() {
int x = 10, y = 5;
swapXY(x,y) = 0;
return 0;
}
返回列表
string ss(bool state)
{
if (state)
return {"Ok"};
else
return {"Error", "Exit"};
}
返回指向数组的指针
typedef int arr[10];
using arr = int[10];
arr* func(int i);
int (*func(int i)) [10];
auto func(int i) -> int(*)[10];
int arr[10] = {1,2,3};
decltype(arr) *func(int i)
{
}
int overLoad(int a); // 原函数
int overLoad(int); // 变量名可以忽略
typedef int INT;
int overLoad(INT a); // int 和 INT是相同的类型
float overLoad(int a); // error: 只有返回值不同
int overLoad(const int); // top-level const对于可以传递的实参的类型没有影响
const int &go(const int &a)
{
return a;
}
int &go(int &a)
{
return const_cast(go(const_cast(a)));
}
非const版本的实现调用了const版本,这是一种不错的重载函数实现方法。因为
同名的重载函数一般都完成相同的任务,利用其中一个去实现其他的,这样在函数需要修改时,就很方便了。
void go(int, int = 10, int = 5);
void go(int i0, int i1 = 10, int i2 = 5)
{
}
如果一个参数具有默认值,那么其后的所以参数也需要提供默认值。这种规定可以使我们在调用函数时,
全部或部分省略有默认值的参数。
go(10);
go(10,20);
go(10,20,30);
【技巧】在设计函数时,将最不常使用默认值得参数放在前面,最常使用默认值的放在后面。
inline int maxXYZ(int x, int y, int z)
{
return x > y ?
(x > z ? x : z):
(y > z ? y : z);
}
#define assert(_Expression) (void)( (!!(_Expression))
|| (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) )
该宏用来测试一个表达式的值是否为真。如果为假,则会中断程序的执行,并提示出错的地方。
void go();
void go(int);
void go(int, int);
void go(double, double = 1.0);
...
go(3.14);
函数调用会和
go(double, double = 1.0);匹配。
bool cmp(const void *a, const void *b);
bool (*pf)(const void *a, const void *b) = cmp;
函数指针必须赋值为与其类型相同的函数,不能指向不同类型的函数。当然,函数指针可以指向重载函数,可以作为函数的参数或返回值。由于函数指针的声明比较复杂,我们可以利用新标准的decltype和auto关键字来简化。
typedef decltype(cmp) FUNC_TYPE; //
函数指针作为函数返回值:
FUNC_TYPE *getCmpFunc(); // decltype返回的是函数类型而不是函数指针
using PFUNC_TYPE = bool (*)(const void *, const void *);
PFUNC_TYPE getCmpFunc();
auto getCmpFunc() -> bool (*)(const void *, const void *);