函数——c++的编程模块
函数的基本知识
C++对于函数的返回类型有一定的限制:不能是数组,但可以是其它的任意数据类型(整数,浮点,指针,甚至结构和对象,结构中包含数组也可以)
函数参数和按值传递
注意:(109)/(21) = 90/2 = 45
(10/2)*(9/1) = 45;这两种方法得到的结果相同,但前者的中间值90大于后者。因子越多,中间值的差值就越大。当数字非常大时,这种交替进行乘除运算的策略可以防止中间的结果超出最大浮点数。
函数和数组
C++将数组名解释为第一个元素的首地址。对数组名使用sizeof得到的是整个数组的长度(字节为单位)。当且仅当用于函数头或者函数原型中,int a 和int a[] 是等价的。
a[i] = =(a+i)
&a[i] = a+i;
using namespace std;
int sum(int* a, int n)
{
cout << a << " address " << endl;
cout << "sizeof : " << sizeof(a) << endl; // 此处每次是4
int total = 0;
for (int i = 0; i < n; i++)
{
total += a[i];
}
return total;
}
int main()
{
int a[5] = { 1,2,3,4,5 };
cout << a << " address " << endl;
cout << "sizeof : " << sizeof(a) <<endl; //20
int count = sum(a, 2);
cout << "count :" <<count<< endl; // 3
count = sum(a+2, 2); //sum(&a[2],2) 一样
cout << "count :" << count << endl; // 7
}
指针和const
- 指针指向一个常量对象
int age = 20;
const int * pa = &age;
不能用pa修改age的值。pa的值为const,不能被修改。pa的声明并不意味着他指向的值实际上是一个常量,而是意味着对pa而言这个值是常量。pa指向age,age不是const。因此pa = 20; //error age = 20; //ok - 指针本身声明为常量
const int age = 20;
const int * pa = &age; //ok
const int age = 20;
int * pa = &age; //error 可以使用pa修改age。C++禁止这种,如果非要这样,使用强制转换。
注意:1.仅当有一层间接关系(如指针指向基本的数据类型),才可以将非const的地址或指针,赋给const指针。- const int a[2] = {0};
int sum(int* a, int n);
sum(a,2);//禁止
- const int a[2] = {0};
尽量使用const
- 可以避免由于无意间修改数据而导致的编程错误
- 使用const 使得函数能够处理const 和非const 实参,否则将只能接受非const数据。
- int age = 20;
const int * pt = &age; const 只能防止修改pt指向的值(20),而不能修改pt的值。即可以将新地址赋给pt。
int sag = 0;
pt = &sag;但仍然不能使用pt来修改指针的值。 - int slo = 3;
const int ps = &slo; //指针指向const int 不允许使用 ps修改slo的值,允许ps指向另一个位置。
int const finger =&slo; //const 指针指向int ,只能指向slo,但允许使用finger修改slo的值
即:finger 和ps 都是const
const 放在左边表示指向的值不可以通过指针修改,*右边表示指针指向的值不能修改。
函数和二维数组
int *a[4] ; 指针数组。 由4个指向int的指针组成的数组
int (*a) [4] ; 数组指针 一个指向由4个int组成的数组的指针。 (数组指针,指向一个数组,该数组由4个int 组成)
int a[3][4] ; a是一个数组名,该数组有三个元素,第一个元素本身又是一个数组,由4个int组成。
因此 函数声明:int sum(int (*a) [4] ,int size)或者int sum(int a【】 [4] ,int size);
上述两个声明都指出a是指针而不是数组。指针类型指出由4个int组成的数组,因此指定了列数。没有指定函数。
int a[100][4];
int b[6][4];
sum(a,100);
sum(a,10);
//指针类型指定了列数,只能接受4列的二维数组,但对行数没有限制
//没有使用const 因为这种技术只能用于指向基本类型的指针,而a是指向指针的指针。
int sum(int (*a)[4],int row)
{
int total = 0;
for(int i=0;i<row;i++)
{
for(int j=0;j<4;j++)
{
total += a[i][j]; //
}
}
return total;
}
//a 指向数组的第一个元素(它的元素由4个int组成),因此a+2 表示编号为2的元素。又该元素是一个由4个int组成的数组,因此a【2】是由4个int元素组成的数组名称。a【2】【1】 一个元素的值。或者 *(*(a+2)+1)
//a 指针 指向第一行的由4个int 组成的数组
//a +r 指针指向第r行,该行由4个int组成
//*(a +r) 第r行的指针 a【r】
// *(a +r)+c 第r行第c列的指针 a【r】 +c
// *(*(a +r)+c) 第r行第c列的值 a【r】【c】
函数和C风格字符串
C风格字符串由一系列字符组成,以空值字符结尾。将字符串作为参数时意味着传递的是地址,但可以使用const禁止对字符串参数进行修改。
将C风格的字符串作为参数的函数
要将字符串作为参数传递给函数,则表示字符串的方式有三种:
- char 数组
- 用引号括起的字符串常量(也称字符串字面值)
- 被设置为字符串的地址的char指针
char ghost[15] = “helloworld”;
char *str = “helloword”;
int n1 = strlen(str); //指针 char
int n2 = strlen(ghost); //ghost is &ghost【0】
int n2 = strlen(“hello”); string的地址
将字符串作为参数传递,实际是传递的字符串第一个字符的地址,由于有\0结尾,因此不必传递长度给函数。
char 数组与常规C风格字符串的区别,字符串有内置的结尾符,若数组没有,则只是数组,不是字符串。
unsigned int c_in_str(const char* str, char ch)
{
unsigned int count = 0;
while (*str)
{
if (*str == ch)
{
count++;
}
str++;
}
return count;
}
int main()
{
char mm[15] = "minmumn";
const char* wail = "ululuuu";
unsigned int m = c_in_str(mm,'m');
unsigned int u = c_in_str(wail, 'u');
cout << m << endl;
cout << u << endl;
}
返回C风格字符串的函数
函数无法返回一个字符串,但能返回一个字符串的首地址
char* bulid(char ch, int n)
{
char* pstr = new char[n + 1]; //作用域在函数内,函数结束时pstr使用的内存将被释放,但由于
pstr[n] = '\0'; //函数返回了pstr,因此可以在main函数中的指针ps来访问该段内存。
while (n-->0) //从后向前是为了使用额外的变量
{
pstr[n] = ch;
}
return pstr;
}
int main()
{
char* ps = bulid('w', 7);
cout << ps << endl;
delete [] ps;
}
函数和结构
缺点:如果结构非常大,则复制结构将增加内存要求,降低系统运行的速度。可以使用地址或者引用。
struct polar {
double distance;
double angle;
};
struct rect {
double x;
double y;
};
polar rect_to_polar(rect re)
{
polar p;
p.distance = sqrt(re.x * re.x + re.y * re.y);
p.angle = atan2(re.y ,re.x);
return p;
}
void show_polar(polar p)
{
const double Rad_to_deg = 57.29;
cout << "distance :" << p.distance;
cout << "degree : " << p.angle * Rad_to_deg;
}
int main()
{
rect r{ 5, 5 };
polar p = rect_to_polar(r);
show_polar(p);
}
- while(cin>>r.x>>r.y)
- for(int i=0;i<n;i++)
{
cin>>tmp;
if(tmp<0) break; //提早结束循环输入一个负值
}
cin消除了限制,接受任何有效的数字输入。此外,非数字输入将设置一个错误条件,禁止进一步读取输入。(使用clear取消禁止)
传参结构体地址
void rect_to_polar(const rect *re,polar *p)
{
p->distance = sqrt(re->x * re->x + re->y * re->y);
p->angle = atan2(re->y ,re->x);
}
函数和string对象
函数和array对象
递归
自己调用自己
调用一次导致两个调用,导致4个,导致8个,6层调用填充64个元素的原因 2^6 = 64
void SubDivide(char a[], int low, int high, int level)
{
if (level == 0)
{
return;
}
int mid = (low + high) / 2;
a[mid] = '|';
SubDivide(a, low, mid, level - 1);
SubDivide(a, mid, high, level - 1);
}
int main()
{
const int len = 66;
const int Divs = 6;
char ruler[len];
for (int i = 1; i < len - 2; i++)
{
ruler[i] = ' ';
}
ruler[len - 1] = '\0';
int max = len - 2;
int min = 0;
ruler[min] = ruler[max] = '|';
cout << ruler << endl;
for (int i = 1; i < Divs; i++)
{
SubDivide(ruler, min, max, i);
cout << ruler << endl;
for (int j = 1; j < len - 2; j++)
{
ruler[j] = ' ';
}
}
}
输出:
函数指针
函数的地址是存储其机器语言代码的内存的开始地址。可以编写将另一个函数的地址作为参数的函数,这样第一个函数就能找到第二个函数,并运行它。与直接调用另一个函数相比,这种方法很笨拙,但它允许在不同的时间传递不同函数的地址,意味着可以在不同的时间使用不同的函数。
一个函数中调用其他功能相同,实现不同的函数
- 获取函数地址。
若f()为函数,则将函数作为参数传递的方法: fb(f) //直接为函数名称,不加(),加了则默认的是函数的返回值为参数 - 声明函数指针
声明指向某种数据类型的指针时,必须制定指针指向的类型。同样,声明指向函数的指针时,也必须指定指针指向的函数类型。
若函数原型如: double f(int a); 则函数指针double (*pf) (int); (*pf) 代替了f , (*pf) 就是函数,pf就是函数指针
定义了函数指针之后,将函数地址赋给指针。 pf =f; 特征标必须相同 - 使用指针调用函数
double x = f(3); 或者 x= (*pf)(3); C++也允许x= pf(3);
深入探讨函数指针
const double* f1(const int* , int );
const double* f2(const int [], int);
const double* f1(const int a[], int n);
三个函数声明完全相同,若要定义则必须加标识符
声明一个函数指针,并在声明的时候初始化
const double* (p1)(const int, int) = f1;
可以使用C++11的自动类型推断功能
auo p2 = f1;
//
cout<< (p1)(av,3)<<" : "<<(*p1)(av,3)<<endl;
cout<< p2(av,3)<<" : "<<p2(av,3)<<endl;
两者都调用指向的函数f1和f2,并将av和3,作为参数。返回const double,因此,前面打印返回double值得地址,后面显示地址的值。
- 含有三个函数指针的数组
const double* (pa【3】)(const int , int ) = {f1,f2,f3};
一方面pa是一个包含三个元素的数组,因此pa【3】,该声明的其它部分已经说明数组包含的元素是什么样子了。【】的优先级高于*,因此*pa【3】表示pa是一个包含3个指针的数组,其中每个指针都指向这样的函数,返回…,参数…。
注意:此处不能使用auto,auto只能用于单值初始化,而不能用于初始化列表。但声明pa后,可以auto pb = pa; - 调用 const double * a = pa0; 或者 const double * a = (pb【1】)(av,3);
获得double的值可以pa0; *(*pb【1】)(av,3) - 创建一个指向数组的指针
auto pc = &pa // c++11
const double* (*(pd)【3】)(const int , int ) = &pa;
调用:pd指向数组,*pd就是数组,(pd)【i】是 数组中的元素,即函数指针。因此简单的调用.(pd)i; 指向的值(pd)i
也可以((pd)【i】)(av,3)调用函数。获得返回的值:((*pd)【i】)(av,3)。
注意:pa (是数组名,表示地址)和&pa之间的差别。大多数情况下,pa是第一个元素的地址,即&pa【0】,它是单个指针的地址。&pa是整个数组的地址(3个指针块)。从数字上说,两者的地址值相同,但+1后,前者为数组中元素的下一个地址,后者为下一个12字节的聂村块地址。另一个差别是,要得到第一个元素的值,只需要对pa解除一次引用。但对&pa需要解除两次引用。
**&pa == *pa == pa[0];
const double* f1(const double* ar, int) { return ar; }
const double* f2(const double ar[], int) { return ar+1; }
const double* f3(const double ar[], int n) { return ar+2; }
int main()
{
double av[3] = { 5.6, 8.9, 9.9 };
const double* (*p1)(const double*, int) = f1;
auto p2 = f2;
cout << "using pointer to functions \n";
cout << "adress value " << (*p1)(av, 3) << " : " << *(*p1)(av, 3) << endl;
cout << "adress value " << p2(av, 3) << " : " << *p2(av, 3) << endl;
//函数指针数组
const double* (*pa[3])(const double*, int) = { f1,f2,f3 };
auto pb = pa;
cout << "函数指针数组\n";
for (int i = 0; i < 3; i++)
{
cout << pa[i](av, 3) << " : " << *pa[i](av, 3) << endl;
cout << pa[i](av, 3) << " : " << *pa[i](av, 3) << endl;
}
auto pc = &pa;
cout << (*pc)[2](av, 3) << " : " << *(*pc)[2](av, 3) << endl;
const double* (*(*pd)[3])(const double*, int) = &pa;
const double* pdb = (*pd)[1](av, 3);
cout << pdb << " : " << *pdb << endl;
cout << (*(*pd)[2])(av, 3) << " : " << *(*(*pd)[2])(av, 3) << endl;
}
使用typedef进行简化
typedef const double* (p_fun)(const double, int) ;
p_fun p1 = f1;
p_fun pa[3] = {f1,f2,f3};
p_fun (*pd)[3] = &pa;
使用typedef可减少输入量,让编写代码时不易犯错,并让程序更容易理解。