文章目录
前言
本人在阅读C++ primer plus(第六版)的过程中做的笔记,写这篇文章既是为了分享,也是为了以后查阅。以下是本篇文章正式内容。
一、函数基本知识
函数返回类型不能是数组,可以是其它任何类型(虽然不能直接返回数组,但可以将数组作为结构或对象的组成部分来返回)。
为什么需要函数原型?原型描述了函数到编译器的接口,它将函数返回值的类型,参数类型和参数数量告诉编译器。
二、函数和数组
- 数组声明使用数组名来标记存储位置;对数组名使用sizeof将得到整个数组的长度(以字节为单位);将地址运算符&用于数组名将得到整个数组的地址。
- 非const变量的地址可以赋给非const指针;
const变量的地址可以赋给const指针;
非const变量的地址可以赋给const指针;
const变量的地址不可以赋给非const指针;
非const变量的地址赋给const指针时,如:
int n = 9;
const int *pn = &n;
则不可以通过指针pn修改n的值,但是可以将新值直接赋给n,如:
*pn = 10; //不允许
n = 10; //允许
也可以将新的地址赋给const指针,如:
int m = 20;
pn = &m;
也可以使用const使指针的值无法修改,如:
int * const pr = &n;
这样,pr就只能指向n的地址而不能修改,但是可以通过pr来修改n的值。
还可以声明指向const对象的const指针,如:
const int * const pt = &n;
三、函数和二维数组
函数的参数不能是数组,但可以是指针,幸运的是数组名即是数组的地址。在将二维数组作为函数的参数时,比较难处理的是如何在函数原型中正确地声明指针。假如有下面的二维数组:
int data[3][4] = {{1, 2, 3, 4}, {9, 8, 7, 6}, {2, 4, 6, 8}};
data是一个数组名,该数组有3个元素,第一个元素本身是一个由4个int组成的数组,所以data指向由4个int组成的数组。
在将data作为函数的参数前,需要搞清楚下面两个声明语句的含义:
int (*ar2)[4]; //声明1
int *ar2[4]; //声明2
在声明1中,ar2是一个指针,指向的是由4个int组成的数组;在声明2中,ar2是一个数组,由4个int指针组成。因此,将data作为函数参数时,正确的函数原型如下:
int sum(int (*ar2)[4], int size);
还有另一种格式,这种格式与上述原型的含义完全相同,但可读性更强:
int sum(int ar2[][4], int size);
指针类型指定了列数,因此sum()函数只能接受由4列组成的数组,但size参数指定了行数。由于参数ar2是指向数组的指针,那么如何在函数定义中使用它呢?最简单的方法是将ar2看作是一个二维数组的名称。下面是一个可行的函数定义:
int sum(int ar2[][4], int size)
{
int total = 0;
for (int r = 0; r < size; r++)
for (int c = 0; c < 4; c++)
total += ar2[r][c];
return total;
}
行数被传递给size参数,但无论是参数ar2的声明或是内部for循环中,列数都是固定的4列,因此sum()函数对数组的行数没有限制:
int a[100][4];
……
int total1 = sum(a, 100); //数组a所有元素的和
int total2 = sum(a, 10); //数组a前10行元素的和
int total3 = sum(a + 10, 20); //数组a从第10行到第20行元素的和
可以使用数组表示法的原因如下。由于ar2指向数组,因此表达式ar2+r指向编号为r的元素,即ar2[r]是编号为r的元素。该元素本身就是一个由4个int组成的数组,因此ar2[r]是该数组的名称。将下标用于数组名将得到一个数组元素,因此ar2[r][c]是由4个int组成的数组中的一个元素,是一个int值。必须对指针ar2执行两次解除引用才能得到数据,最简单的方法是使用两次方括号:ar2[r][c]。然而,如果不考虑难看的话也可以使用两次解除引用运算符*:ar2[r][c] == ((ar2 + r) + c);
四、函数和C风格字符串
1.C风格字符串为参数
unsigned int c_int_str(const char *str, char ch); //函数原型
unsigned int c_int_str(const char *str, char ch) //函数定义
{
unsigned int count = 0;
while (*str)
{
if (*str == ch)
{
count++;
str++;
}
}
return count;
}
将字符串作为参数来传递,但实际传递的是字符串第一个字符的地址。这意味着字符串函数原型应将其表示字符串的形参声明为char*类型。
2.返回C风格字符串
char* buildstr(char ch, int n); //函数原型
char* buildstr(char ch, int n) //函数定义
{
char* pstr = new char[n + 1];
pstr[n] = ‘\0’;
while (n-- > 0)
{
pstr[n] = ch;
}
return pstr;
}
在while循环中,执行到测试条件时,假如此时n = 1,则执行到括号结束时n = 0,因为后缀递减运算是先使用,后递减,即先判断1 > 0,执行循环体之前再将1减1。
五、递归
1.包含一个递归调用的递归
例如,void类型的递归函数recurs()的代码如下:
void recurs(argumentlist)
{
int n;
statements1;
if (testistrue)
recurs(arguments);
statements2;
}
只要if语句为true,每个recurs()调用都将执行statements1,然后再调用recurs(),而不会执行statements2。当if语句为false,当前调用执行statements2。当前调用结束后,程序控制权返回给调用它的recurs(),而该recurs()将执行statements2,然后结束,并将程序控制权交给前一个调用,以此类推。因此,如果recurs()进行了5次递归调用,则statements1将按调用顺序执行5次,而statements2按与函数调用相反的顺序执行5次,5层递归后程序将沿进入的路径返回。每个递归调用都创建自己的一套变量,因此当程序到达5次调用后,将有5个独立的变量n。分析下面的代码:
#include<iostream>
using namespace std;
void recurs(int);
int main()
{
recurs(4);
return 0;
}
void recurs(int n)
{
cout << "Counting down..." << n << endl;
if (n > 0)
recurs(n - 1);
cout << n << ": Kaboom!\n";
}
运行结果如下图:
2.包含多个递归调用的递归
分析下面代码:
#include<iostream>
using namespace std;
const int Len = 66;
const int Divs = 6;
void subdivide(char ch[], int left, int right, int level);
int main()
{
char ruler[Len];
ruler[Len - 1] = '\0';
ruler[0] = ruler[Len - 2] = '|';
for (int i = 1; i < Len - 2; i++)
ruler[i] = ' ';
cout << ruler << endl;
for (int i = 1; i < Divs + 1; i++)
{
subdivide(ruler, 0, Len - 2, i);
cout << ruler << endl;
for (int j = 1; j < Len - 2; j++)
ruler[j] = ' ';
}
return 0;
}
void subdivide(char ch[], int left, int right, int level)
{
if (level == 0)
return;
int mid = (left + right) / 2;
ch[mid] = '|';
subdivide(ch, left, mid, level - 1);
subdivide(ch, mid, right, level - 1);
}
运行结果如下图:
六、函数指针
1.函数指针的基础知识
函数的地址是存储其机器语言代码的内存的开始地址。获取函数的地址很简单,只要使用函数名(后面不跟参数)即可,例如think()是一个函数,则think就是该函数的地址。声明函数指针必须指定函数的返回类型以及函数的特征标(参数列表),应该像声明函数原型那样声明函数指针。
假如有函数double pam(int);
则其函数指针声明为double (*pf)(int);
pf就是其函数指针,可以将相应的函数地址赋给它,例如pf = pam;
pam的特征标和返回类型必须和pf相同,否则编译器将拒绝这种赋值。
使用方式:
void estimate(int lines, double (*pf)(int));
estimate(50, pam);
详情见下面代码:
#include<iostream>
using namespace std;
double leon(int);
double leonbro(int);
void estimate(int, double(*pf)(int));
int main()
{
int lns;
cout << "输入需要计算的行数:";
cin >> lns;
cout << "leon计算出来的时间:";
estimate(lns, leon);
cout << "leonbro计算出来的时间:";
estimate(lns, leonbro);
return 0;
}
double leon(int lines)
{
return 0.05 * lines;
}
double leonbro(int lines)
{
return 0.03 * lines + 0.004 * lines * lines;
}
void estimate(int lines, double(*pf)(int))
{
cout << (*pf)(lines) << " hours." << endl;
}
2.函数指针数组、指向函数指针数组的指针
首先,有3个函数:
const double * f1(const double ar[], int n);
const double * f2(const double [], int);
const double * f3(cosnt double*, int);
这3个函数是完全相同的。
假设指针pa指向这3个函数之一,只需将函数名替换为(*pa):
const double * (*pa)(const double *, int);
可在声明时初始化:
const double * (*pa)(const double , int) = f1;
也可以使用自动类型推断:auto pa = f2;
声明一个函数指针数组包含这3个函数:
const double * (*pa[3])(const double *, int) = {f1, f2, f3};*pa[3]表明这是一个有3个元素的指针数组,其他部分指出了每个指针指向的类型是什么。
另一件事是创建指向整个数组的指针,使用auto:auto pc = &pa;
也可以自己声明:
const double * ( *(*pd)[3])(const double *, int) = &pa;
pd指向数组,*pd就是数组,而(*pd)[i]就是数组中的元素,即函数指针。
分析下面的代码:
#include<iostream>
using namespace std;
const double* f1(const double ar[], int n);
const double* f2(const double*, int);
const double* f3(const double[], int);
int main()
{
const double arr[3] = { 0.0, 1.1, 2.2 };
const double* (*arr_fun_ptr[3])(const double*, int) = { f1, f2, f3 };//函数指针数组
cout << fixed;
cout.precision(1);
for (int i = 0; i < 3; i++)
cout << (*arr_fun_ptr[i])(arr, 3) << " " << *(*arr_fun_ptr[i])(arr, 3) << endl;
return 0;
}
const double* f1(const double ar[], int n)
{
return ar;
}
const double* f2(const double* ar, int n)
{
return ar + 1;
}
const double* f3(const double ar[], int n)
{
return ar + 2;
}
3.使用typedef进行简化
关键字typedef可以创建类型别名,因此也可以创建函数指针类型的别名:
typedef const double* (*p_fun)(const double ar[], int); //p_fun现在是函数指针类型
然后使用这个别名来简化代码:
p_fun p1 = f1; //p1指向函数f1()
p_fun arr[3] = {f1, f2, f3}; //arr[3]是函数指针数组
总结
以上就是本文的内容——记录了函数基本知识、函数和数组以及二维数组、函数中的C风格字符串、递归和函数指针。