betway必威-betway必威官方网站
做最好的网站

C语言学习笔记,对C语言的非面向对象特性增添

  上一篇随笔写了关于C 在注释,输入输出,局部变量说明的扩充,以及const修饰符与C中的#define的比较,也得到了几位学习C 朋友们的帮助讲解,十分感谢,我也希望欢迎有更多学习C 的朋友一起来讨论,这样大家都能共同进步。那么,今天这篇要讲的是C 在函数原型上和C的区别、内联函数、带有默认参数的函数以及函数的重载。

1程序模块化

一、内联函数

  1.大家都熟悉在C中,如果函数调用的位置在函数定义之前,那么在函数调用之前要对函数原型声明或调用之前就把函数直接定义好了。比如:

 

  1、内联函数的机制

#include<stdio.h>

在程序设计过程中大多数程序要比我们之前设计的程序复杂的多,传统的设计方法是“自定向下,逐步求精”的过程。该过程就是将一个大的问题按照层次分解成多个方便解决的小问题,直至各个功能模块,每个单独的功能模块可以单独设计,最后将所有的功能模块有机的结合成完整的程序。

  内联函数是C 为提高程序运行速度而做的一项改进。

int add(int x,int y);

 

  函数调用机制:常规函数调用使程序使程序跳到被掉函数的地址,并在函数结束时返回。

int main()

例子:计算出该日是该年的第几天。

  内联函数的机制:内联函数的代码与其他的程序代码内联起来,即编译器将使用函数的代码替换函数调用。对于内联代码,程序无需跳到另外一处执行代码,再跳回来。因此,内联函数的运行速度比常规函数快,但代价是需要占用更多内存。

{

 

  2、使用内联函数

  int x,y,sum;

问题可以分解为:获取输入;判断平年闰年;判断每个月的总天数;得到总天数;

  要使用内联函数,必须采取下列措施:

  printf("请输入两个整数:n");

 

  * 在函数声明前加上关键字inline;

  scanf("%d,%d",&x,&y);

例子代码:

  * 在函数定义前加上关键字inline。

  sum=add(x,y);

 

  通常的做法是省略原型,将整个定义放在本应提供原型的地方。注意,内联函数不能递归。

  printf("x y=%d",sum);

 

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 inline int sum(int x, int y){//通常省略内联函数的原型,直接将函数定义放在本应放函数原型的地方;另外,内联函数不能递归。
 6     return x   y;
 7 }
 8 int main(int argc, const char * argv[]) {
 9     
10     cout << sum(12,32) << endl;
11     
12     return 0;
13 }
14 
15 输出结果:
16 44

  return 0;

#include "stdio.h" 

  补充:

/*(1)判断闰年*/ 

  inline工具C 新增的特性。C语言使用预处理语句#define来提供宏——内联代码的原始实现。例如:

int add(int x,int y)

int leap(int year) 

 1 #include <iostream>
 2 
 3 #define MULT(X) X*X //这不是通过传递参数来实现的,而是通过文本替换来实现的。即在使用该宏的时候,后面的X*X中的X将用宏小括号中的完整文本来替换。如果使用宏时,小括号中的内容为12 21,那么X*X就会被替换成12 21*12+21,而不是先计算12 21的值得到33,然后用33来替换X*X中的X
 4 #define MUTL2(X) (X)*(X)
 5 using namespace std;
 6 
 7 
 8 int main(int argc, const char * argv[]) {
 9     
10     cout << "MULT(12):"<< MULT(12) << "nMULT(6 6):" << MULT(6 6) << endl;
11     cout << "MUTL2(10): " << MUTL2(10) << "nMUTL2(5 5):" << MUTL2(5 5) << endl;
12     return 0;
13 }
14 
15 输出结果:
16 MULT(12):144
17 MULT(6 6):48
18 MUTL2(10): 100
19 MUTL2(5 5):100

{

{int flag; 

二、引用变量   

  return x y;  

if (year%4==0&&year0!=0||year@0==0) 

  引用变量的主要作用是用作函数的形参。通过将引用变量作为形参,函数将使用原始数据,而不是其副本。这样,除了指针。引用也为函数处理大型结构提供了一种非常方便的途径,同时对于设计类来说,引用也是必不可少的。

}

   flag=1; 

   下述的讨论用于说明引用是如何工作的,而不是其典型用法。

不过也可以采用简洁的方式来声明,如:int add(); add(); 都可以通过编译;但是在C 里,如果函数定义在后,调用在前,那函数原型的声明必须是int add(int x,int y);函数名称,参数类型和个数,以及返回值都必须说明;如果函数定义在前,调用在后则和C一样。以上这种形式在C 里也等同于int add(int ,int);如果在原型说明中没有指出返回类型C 默认返回类型为int,不需要返回值,就用void。另外标准C 要求的main函数的返回值必须为int;

else  flag=0; 

  1、创建引用变量

  2.内联函数就是在函数说明前冠以关键字"inline",当C 在编译时使用函数体中的代码插入到要调用该函数的语句之处,同时用实参代替形参,以便在程序运行时不再进行函数调用。比如:

return flag; 

  C 给&赋予了另一个新的含义,将其用来声明引用。例如,要将dog作为animal变量的别名,可以这样做:

#include <iostream>

    int animal;

using namespace std;

/*(2)求某月的天数*/ 

    int & dog = animal;

inline int add(int a,int b)

int month_days(int year,int month){ 

  其中&不是取地址符,而是类型标识的一部分。上述引用声明允许将dog和animal互换——它们指向相同的值和内存单元。

{

   int d; 

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 void print(int & num);
 6 void print2(int num);
 7 int main(int argc, const char * argv[]) {
 8     int mun = 12;
 9     cout << "mun :" << mun << " at " << &mun << endl;
10     print(mun);
11     print2(mun);
12     
13     return 0;
14 }
15 void print(int & num){
16     cout <<"num :"<< num << " at " << &num << endl;
17 }
18 void print2(int num){
19     cout << "num :"<< num << " at " << &num << endl;
20 }
21 
22 输出结果:
23 mun :12 at 0x7fff5fbff7ec
24 num :12 at 0x7fff5fbff7ec
25 num :12 at 0x7fff5fbff7bc

  return a b;

   switch(month)  { 

  注意:必须在声明引用变量时进行初始化。

}

      case 1: 

  同时,由于引用是变量的别名,因此引用与变量等效,例如:

int main()

      case 3: 

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 
 6 int main(int argc, const char * argv[]) {
 7     int num1 = 10;
 8     int num2 = 13;
 9     int & num3 = num1;
10     cout << "num1 = " << num1 << " at " << &num1 << endl;
11     cout << "num2 = " << num2 << " at " << &num2 << endl;
12     cout << "num3 = " << num3 << " at " << &num3 << endl << endl;
13     num3 = num2;//由于num3是num1的引用,num3就相当于num1的别名,num3 = num2 与num1 = num2是等价的
14     cout << "After num3 = num2 :n";
15     cout << "num1 = " << num1 << " at " << &num1 << endl;
16     cout << "num2 = " << num2 << " at " << &num2 << endl;
17     cout << "num3 = " << num3 << " at " << &num3 << endl;
18     return 0;
19 }
20 
21 输出结果:
22 num1 = 10 at 0x7fff5fbff78c
23 num2 = 13 at 0x7fff5fbff788
24 num3 = 10 at 0x7fff5fbff78c
25 
26 After num3 = num2 :
27 num1 = 13 at 0x7fff5fbff78c
28 num2 = 13 at 0x7fff5fbff788
29 num3 = 13 at 0x7fff5fbff78c

{

      case 5: 

  说明:上述例子说明可以通过初始化来设置引用,但是不能通过赋值来设置引用,即,假设C是A的引用,那么就不能通过C = B来将C设置成B的引用。

  int x,y,sum;

      case 7: 

2、将引用作为函数参数

  cin>>x>>y;

      case 8: 

  引用经常被用作函数的参数,使得函数中的变量名称为调用程序中的变量的别名。这种传递参数的方法叫做按引用传递。按引用传递允许被调函数能够访问调用函数的变量。

  sum=add(x,y);

      case 10: 

#include <iostream>

using namespace std;

void change(int & num1,int & num2);//用来交换两个变量中的值
int main(int argc, const char * argv[]) {
    int number1 = 25;
    int number2 = 45;
    cout << "Before change:n";
    cout << "number1 = " << number1 << " at " << &number1 << endl;
    cout << "number2 = " << number2 << " at " << &number2 << endl<< endl;
    cout << "After change:n";
    change(number1, number2);
    cout << "number1 = " << number1 << " at " << &number1 << endl;
    cout << "number2 = " << number2 << " at " << &number2 << endl;
    return 0;
}
void change(int & num1,int & num2){
    num1 = num1   num2;
    num2 = num1 -num2;
    num1 = num1 - num2;
}

输出结果:
Before change:
number1 = 25 at 0x7fff5fbff7ac
number2 = 45 at 0x7fff5fbff7a8

After change:
number1 = 45 at 0x7fff5fbff7ac
number2 = 25 at 0x7fff5fbff7a8

  cout<<"x y="<<sum<<endl;

      case 12:d=31;break; 

 

  return 0;

      case 2: d=leap(year)?29:28;break;     /*通过调用函数计算,闰年29天,平年28天*/ 

  说明:前面说过,应在定义引用时对其进行初始化。函数调用使用实参初始化形参,因此函数的引用参数被初始化函数调用传递的实参。

}

      default:d=30;break; 

3、引用的属性和特别之处   

在编译时,遇到函数啊add(x,y)时,用函数体代替add(x,y),同时实参代替形参,这样“sum=add(x,y)”被替换成“{

  } 

  

int a=x;int b=y;sum=a b;}”;那么为什么要引入内联函数呢?主要是为消除函数调用时的系统开销,以提高系统的运行速度。在程序执行过程中调用函数,系统要将程序当前的一些状态信息存到栈中,同时转到函数的代码处去执行函数体的语句,这些参数保存和传递过程中需要时间和空间的开销,使得程序效率降低。但是并不是什么函数都可以定义为内联函数,一般情况下,只有规模很小而是用频繁的函数才定义为内联函数,这样可以大大提高运行速率。

  return d; 

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 double cube(double x);
 6 double refcube(double & rx);//这两个函数都用来计算立方
 7 int main(int argc, const char * argv[]) {
 8     double num = 4.0;
 9     cout << cube(num) << " = cube of " << num << endl;
10     cout << refcube(num) << " = cube of " << num << endl;
11     return 0;
12 }
13 double cube(double x){
14     x *= x * x;
15     return x;
16 }
17 double refcube(double & rx){
18     rx *= rx * rx;
19     return rx;
20 }
21 
22 输出结果:
23 64 = cube of 4
24 64 = cube of 64

  3.一般情况下,实参的个数应该和形参的一样,但在C 中则不一定,方法是在说明函数原型时,为一个或多个形参制定默认值,以后调用此函数,如省略其中一实参,C 自动地以默认值作为相应参数的值。比如int add(int x=10,int y=10),那么我们在调用该函数时可以有三种写法:add(50,50)//结果为50 50;add(50)//结果为50 10;add()//结果10 10;这样使函数更加灵活。但要注意的是默认参数必须是在参数列表的最右端,int add(int x,int y=10,int z)这样是错误的,还有不允许某个参数省略后,再给其后的参数指定参数值。如果函数定义在函数调用之后,则函数调用之前需要函数声明,此时必须在函数声明中给出默认值,在函数定义时就不要给出默认值了(因为有的C 编译系统会给出"重复指定默认值"的错误信息)图片 1;

  注意:不能将常量或表达式传给按引用传递的函数。比如:函数原型为void cube(int & num)的cube函数就不能这样使用:cube(12)或者cube(x 3)。

  4.函数的重载,对于这个我想学过C#的朋友一定在熟悉不过了,它意味着,只要函数参数的类型不同,或者参数的个数不同,或者两者兼而有之,两个或两个以上的的函数可以使用相同的函数名。尽管简单,但是我还是想说说它在C 里所要注意的几个问题:1.函数返回值不再函数参数匹配检查之列;2.函数重载与带默认参数的函数一起使用可能引起二义性比如:int fun(int x=0;int y=10){return x y;}和int fun(int r){return r;}这时候我这样调用fun(10);3.如果函数调用给出的实参和形参类型不符,C 会自定执行类型转换,转换成功会继续执行,但是在这种情况下可能会出现不可识别的错误:int add(int x,int y)和long add(long,long),这时候我这样调用add(9.9,8.8);

/*(3)求天数和。*/ 

   

  5.最后还是一样通过一个实例来总结一下今天的内容:

int days(int  year,int month,int day){ 

  临时变量、引用参数和const

 

  int i,ds=0; 

    如果实参和引用参数不匹配,C 将生成临时变量。当前,仅当参数为const引用时,C 才允许这样做。

    1 #include "stdafx.h"
2 #include <iostream>
3 usingnamespace std;
4 
5 int add(int x,int y);//或int add(int,int)
6 
7 inline int sub(int x,int y)//内联函数
8 {
9 return x-y;
10 }
11 
12 double mul(double x=10.0,double y=10.0);//带有默认参数的函数
13 
14 float add(float x,float y)//函数重载
15 {
16 return x y;
17 }
18 
19 int main()
20 {
21 int x,y,result;
22 cout<<"请输入两个整数:";
23 cin>>x>>y;
24 result=add(x,y);
25 cout<<"普通函数(加法):x y="<<result<<endl;
26 
27 cout<<"请输入两个整数:";
28 cin>>x>>y;
29 result=sub(x,y);
30 cout<<"内联函数(减法):x-y="<<result<<endl;
31 
32 double a,b,mul_result;
33 cout<<"请输入两个双精度数:";
34 cin>>a>>b;
35 mul_result=mul(a,b);
36 cout<<"带有默认参数的函数(乘法):a*b="<<mul_result<<endl;
37 
38 float c,d,sum;
39 cout<<"请输入两个单精度数:";
40 cin>>c>>d;
41 sum=add(c,d);
42 cout<<"重载加法函数:c d="<<sum<<endl;
43 
44 return0;
45 }
46 
47 int add(int x,int y)
48 {
49 return x y;
50 }
51 
52 double mul(double x,double y)
53 {
54 return x*y;
55 }

  for(i=1;i<month;i ) 

    (1)什么时候创建临时变量

 结果:

    ds=ds month_days(year,i);       /*函数days调用函数month_days,求各月份对应的天数*/ 

    如果引用参数是const,则编译器在下面两种情况生成临时变量(临时变量只在函数调用期间存在):

图片 2

    ds=ds day; 

      *实参的类型正确,但不是左值;


 return ds; 

      *实参的类型不正确,但可以转换为正确的类型。

    左值:左值参数是可以被引用的数据对象,例如,变量、数组元素、结构成员、引用和解除引用的指针都是左值。

/*主程序中调用各个模块来计算天数的和*/ 

    非左值:非左值包含字面常量(用引号括起来的字符串除外,它们由其地址表示)和包含多项的表达式。

void main() 

    常规变量和const变量都可以视为左值,因为可以通过地址访问他们。但常规变量属于可修改的左值,而const变量属于不可修改的左值。

{ int year,month,day,t_day;  

    如果接受引用参数的函数意图是修改作为参数传递的变量,则创建临时变量将阻止这种意图的实现。对于形参为const引用的函数,如果实参不匹配,则其行为类似按值传递,为确保数据不被修改,将使用临时变量存储值。

    printf("input year-month-day:n"); 

    注意:如果函数调用的参数不是左值或与相应const引用参数的类型不匹配,则C 将创建类型正确的匿名变量,将函数调用的参数的值传递给该匿名变量,并让参数来引用该变量。

    scanf("%d-%d-%d",&year,&month,&day); 

   (2)应尽可能使用const

  /*函数scanf作为输入模块是系统定义的,

    将引用参数声明为常量的数据的引用的理由有三:

     主函数main可以直接调用它*/ 

    *使用const可以避免无意中修改数据的编程错误;

  t_day=days(year,month,day); /*求天数和*/ 

    *使用const使函数可以处理const和非const实参,否则将只能接受非const数据;

  printf("%d-%d-%d is %dth day of the year!n",year,month,day,t_day); 

    *使用const引用是函数能够正确生成并使用临时变量。

    /*函数printf作为输出模块也是系统定义,

    因此,尽可能将引用形参声明为const。

       主函数main可以直接调用*/ 

 

  4、将引用用于结构

2函数的定义

  使用结构引用参数的方式与使用基本变量引用方式相同,只需在声明结构参数时使用引用运算符&即可。

 

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 typedef struct {
 6     string name;
 7     int made;
 8     int attempts;
 9     float persent;
10 }free_throws;
11 
12 void display(const free_throws &ft);
13 void set_pc(free_throws &ft);
14 free_throws & accumulate(free_throws & target,const free_throws & source);
15 
16 int main(int argc, const char * argv[]) {
17     free_throws one = {"Ifelsa Branch",13,14};
18     free_throws two = {"Andor Knott",10,16};
19     free_throws three = {"Minnie Max",7,9};
20     free_throws four = {"Whily Looper",5,9};
21     free_throws five = {"Long Long",6, 14};
22     free_throws team = {"Throwgoods",0,0};
23     free_throws dup;
24     
25     set_pc(one);
26     display(one);
27     accumulate(team, one);
28     display(accumulate(team, two));
29     accumulate(accumulate(team, three), four);
30     display(team);
31     dup = accumulate(team, five);
32     cout << "DisPlay team:n";
33     display(team);
34     cout << "Display dup after assignment:n";
35     display(dup);
36     set_pc(four);
37     accumulate(dup, five) = four;
38     cout << "Display dup fter ill-advised assignment:n";
39     display(dup);
40     return 0;
41 }
42 void display(const free_throws &ft){
43     cout << "Name:" << ft.name << endl;
44     cout << "Made:" << ft.made << endl;
45     cout << "Attempts: " << ft.attempts << endl;
46     cout << "Percent:" << ft.persent << endl;
47 }
48 void set_pc(free_throws &ft){
49     if (ft.attempts != 0) {
50         ft.attempts = 100.0f * float(ft.made)/float(ft.attempts);
51     }
52     else{
53         ft.attempts = 0;
54     }
55 }
56 free_throws & accumulate(free_throws & target, const free_throws &source){
57     target.attempts  = source.attempts;
58     target.made  = source.made;
59     set_pc(target);
60     return target;
61 }
62 
63 输出结果:
64 Name:Ifelsa Branch
65 Made:13
66 Attempts: 92
67 Percent:0
68 Name:Throwgoods
69 Made:23
70 Attempts: 76
71 Percent:0
72 Name:Throwgoods
73 Made:35
74 Attempts: 79
75 Percent:0
76 DisPlay team:
77 Name:Throwgoods
78 Made:41
79 Attempts: 44
80 Percent:0
81 Display dup after assignment:
82 Name:Throwgoods
83 Made:41
84 Attempts: 44
85 Percent:0
86 Display dup fter ill-advised assignment:
87 Name:Whily Looper
88 Made:5
89 Attempts: 55
90 Percent:0

2.1函数简介:函数是C语言程序的基本模块,函数一般可以从3中角度进行分类:

  讨论:

 

  (1)为何要返回引用?

从函数定义角度:

  传统返回机制和按值传递机制相似,将关键字return后的返回值(如果为表达式,会计算表达式的值并将值反回)拷贝到一个临时位置,调用函数实际上使用的是被调用函数返回值的一个拷贝。但在返回值为引用时,直接把该引用给调用函数(返回值为引用的函数实际上被引用变量的别名),省略拷贝到临时位置这一步。相比传统返回机制,返回引用更加快速,更加节省内存。

 

  (2)返回值为引用时需要注意的问题

1库函数:库函数由C系统提供,用户无需定义,可以直接调用。

    *返回值为引用时最重要的一点是,应避免返回函数终止时不再存在的内存单元的引用。同样,也应避免返回指向临时变量的指针。例如,应避免编写下面这样的代码:

 

      int & clone(int &a){

2用户自定义函数:用户自己编写的函数,在调用的函数中还必须对被调用函数进行类型说明才能使用。

         int c;

 

         c = a ;

从返回值角度看:

         return c;

 

      }  

1有返回值的函数:有返回值的函数在被调用后将向调用者返回一个执行结果,这个结果就是返回值,用户在定义这种函数时应该说明返回值类型。

      说明:该函数返回一个指向临时变量(c)的引用,函数结束时它将不复存在。为避免这种情况,最简单的做法是,返回一个作为参数传递给函数的引用。作为参数的引用将指向调用函数使用的数据,因此返回的引用也将指向这些数据。另一种办法是用new来分配新的内存空间,但是在使用完了以后需要delete。

 

  (3)将const用于引用返回类型

2无返回值函数:无返回值函数用于完成某项特定的功能,执行完成后不用返回函数值,用户定义时声明这种函数的返回值为空类型,即void。

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 int & change(int &);
 6 int main(int argc, const char * argv[]) {
 7     
 8     int m = 12;
 9     int n = 15;
10     change(m) = n;//由于函数change()返回的是一个指向调用函数中m变量的引用,该函数就相当于m的别名
11     cout << "m:" << m << endl;
12 
13     
14     return 0;
15 }
16 
17 int & change(int &m){
18     m  = 5;
19     return m;
20 }
21 
22 输出结果:
23 m:15

 

 

从数据传输来看:

    在赋值语句中,左边必须是可修改的左值。上述例子中change(m)

n之所以能过通过,是因为函数返回指向m的引用,他确实标识的是这样的一个内存块,因此这条语句是合法的。在编程过程中,应该尽量避免这种语句,可以通过将函数返回值类型声明为const,避免上以上情况。

   

  5、将引用用于类对象

  将类对象传递给函数时,C 通常的做法是使用引用。

 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 
 6 string version1(const string & str1, const string & str2);
 7 string & version2(string & str1,const string &str2);
 8 string & version3(const string &str1, const string &str2);
 9 
10 
11 int main(int argc, const char * argv[]) {
12     
13     string str1 = "Good morning!";
14     string str2 = "###";
15     cout <<  " version1(str1,str2):" << version1(str1, str2) << endl;
16     cout << R" (version1(str1,"***"):) ";
17     cout << version1(str1, "***");//虽然"***"是const char*类型,不是string对象,但是可以转换成string对象,这时函数会创建一个临时变量,使引用形参str2指向该临时变量
18     cout << endl << endl;
19     
20     cout << "version2(str1,str2):" << version2(str1, str2) << endl;
21     cout << "               str1:" << str1 << endl << endl;
22     
23     cout << "version3(str1,str2): " ;
24     cout << version3(str1, str2) << endl;
25 
26     
27     return 0;
28 }
29 
30 string version1(const string & str1, const string &str2){
31     string temp;
32     temp = str2   str1   str2;
33     return temp;
34 }
35 string & version2(string &str1, const string &str2){//该函数有一定的副作用,他修改了引用str1指向的内存的值
36     str1 = str2   str1   str2;
37     return str1;
38 }
39 string & version3(const string &str1, const string &str2){//该函数可能会导致系统崩溃,因为该函数返回了一个指向已释放了的内存的引用,temp为临时变量,在函数调用结束时将会被释放,而该函数却反回了指向temp的引用
40     string temp;
41     temp = str2   str1   str2;
42     return temp;
43 }
44 
45 输出结果:
46  version1(str1,str2):###Good morning!###
47 version1(str1,"***"):***Good morning!***
48 
49 version2(str1,str2):###Good morning!###
50                str1:###Good morning!###
51 
52 version3(str1,str2): ######Good morning!######

  6、何时使用引用参数

   使用引用参数的主要原因有两个:

    *程序员能够修改调用函数中的数据;

    *通过传递引用而不是整个数据对象,可以提高程序的运行速度。

  

   对于使用传递的值而不做修改的函数:

    *如果数据对象很小,如内置数据类型和小型结构,则按值传递

    *如果对象数据是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针

    *如果数据对象是较大的结构,则使用const指针或const引用,以提高程序效率。这样可以节省复制对象的时间和空间。

    *如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用,传递类对象的标准方式是按引用传递。

   

 

   对于修改调用函数中数据的函数:

    *如果数据对象是内置数据类型,则使用指针。如果看到诸如fixit(&x)这样的代码(其中x是int),则很明显,该函数将修改x。

    *如果数据对象时数组,则只能使用指针

    *如果数据对象是结构,则使用引用或指针

    *如果数据对象是类对象,则使用引用。

  

 

三、默认参数

  默认参数:指的是当函数调用中省略了实参时自动使用的一个值。默认参数可以用来实现使用不同数目的参数调用同一个函数。

  设置默认参数:由于编译器通过查看函数原型来了解函数所使用的参数数目,因此函数原型必须将可能的默认参数告知编译器。方法是将值赋给原型中的参数。

 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 void print(string str = "Hello World!");//设置默认参数
 6 int main(int argc, const char * argv[]) {
 7     
 8     print();
 9     print("nihao");
10     
11     return 0;
12 }
13 
14 void print(string str){//函数定义和没有默认参数时一样
15     cout << str << endl;;
16 }
17 
18 输出结果:
19 Hello World!
20 nihao

  

四、函数重载(函数多态)

   1、函数重载

    函数重载(函数多态)的关键是函数的参数——也称为函数特征标。如果两个函数的参数数目和类型相同,同时参数的排列顺序也相同,则它们的特征标相同,而变量名是无关紧要的。C 允许定义名称相同的函数,条件是它们的特征标不同。如果参数数目和/或参数类型不同,则它们的特征标也不同。

    编译器在检查函数特征标时,将把类型引用和类型本身视为同一个特征标。

    匹配函数时,并不区分const和非const变量。

    是特征标,而不是函数类型(函数返回值类型)使得可以对函数进行重载。

   2、何时使用函数重载

   仅当函数基本上执行相同的任务,但使用不同形式的数据时,才应采用函数重载。

 

五、函数模板

  函数模板是通用的函数描述,也就是说,它们使用范型来定义函数,其中的范型可以用具体的类型来替换。通过将类型作为参数传递给函数模板,可使编译器生成该类型的函数。由于模板允许以范型的方式编写程序,因此有时也被称为通用编程。由于类型使用参数表示的,因此模板特性有时也被称为参数化类型。

  模板并不创建任何函数,只是告诉编译器如何定义函数。

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 template <typename T>//template表明接下来要创建一个函数模板,必须使用尖括号。typename可以用class替换
 6 void change(T &num1, T &num2){
 7     num1 = num1   num2;
 8     num2 = num1 - num2;
 9     num1 = num1 - num2;
10 }
11 
12 template <class T>//class可以用typename替换
13 void show(T, T );//声明函数模板原型
14 
15 int main(int argc, const char * argv[]) {
16   
17     int a = 12;
18     int b = 13;
19     float c = 10.2;
20     float d = 3.5;
21     cout << "Before change:n";
22     show(a, b);//编译器将根据函数模版,来创建对应的函数定义
23     show(c, d);
24     
25     cout << "After change:n";
26     change(a, b);
27     change( c, d);
28     
29     show(a, b);
30     show(c, d);
31     return 0;
32 }
33 
34 template <class AnyType>//提供函数模板定义
35 void show(AnyType num1, AnyType num2){
36     cout << num1 << endl;
37     cout << num2 << endl;
38 }
39 
40 输出结果:
41 Before change:
42 12
43 13
44 10.2
45 3.5
46 After change:
47 13
48 12
49 3.5
50 10.2

 

 

  提示:如果需要多个将同一种算法用于不同类型的函数,请使用函数模板。如果不考虑向后兼容的问题,在声明类型参数时,应使用typename而不是class。并非所有的模板参数都是模板参数类型。

1、模板的重载

  被重载的模板的函数特征标必须不同。

  1 #include <iostream>
  2 #include <string>
  3 
  4 using namespace std;
  5 
  6 template <typename T>
  7 void change(T &,T &);
  8 
  9 template <typename T>
 10 void change(T [],T [],int );//并非所有的模板参数都是模板参数类型
 11 
 12 template <typename T>
 13 void show(T,string);
 14 
 15 template <typename T>
 16 void show(T [],int,string);
 17 
 18 int main(int argc, const char * argv[]) {
 19   
 20     int num1 = 12;
 21     int num2 = 15;
 22     
 23     float fnum1 = 10.5;
 24     float fnum2 = 5.5;
 25     
 26     int arr1[3] {11,22,33};
 27     int arr2[3] {100,200,300};
 28     
 29     float farr1[3] {11.5,22.5,33.5};
 30     float farr2[3] {100.1,200.2,300.4};
 31     
 32     cout << "Before change:n";
 33     show(num1, " num1");
 34     show(num2, " num2");
 35     show(fnum1, "fnum1");
 36     show(fnum2, "fnum2");
 37     show(arr1,3,"arr1");
 38     show(arr2,3,"arr2");
 39     show(farr1,3,"farr1");
 40     show(farr2,3,"farr2");
 41     
 42     cout <<endl<< "After change:n";
 43     change(num1, num2);
 44     change(fnum1, fnum2);
 45     change(arr1, arr2, 3);
 46     change(farr1, farr2, 3);
 47     
 48     show(num1, " num1");
 49     show(num2, " num2");
 50     show(fnum1, "fnum1");
 51     show(fnum2, "fnum2");
 52     show(arr1,3,"arr1");
 53     show(arr2,3,"arr2");
 54     show(farr1,3,"farr1");
 55     show(farr2,3,"farr2");
 56     
 57     return 0;
 58 }
 59 
 60 template <typename T>
 61 void change(T & num1, T & num2){
 62     num1 = num1   num2;
 63     num2 = num1 - num2;
 64     num1 = num1 - num2;
 65 }
 66 
 67 template <typename T>
 68 void change(T arr1[], T arr2[],int count){
 69     for (int i = 0; i < count; i  ) {
 70         change(arr1[i], arr2[i]);
 71     }
 72 }
 73 
 74 template <typename T>
 75 void show(T num, string str){
 76     cout << str << ":" << num << endl;
 77 }
 78 
 79 template <typename T>
 80 void show(T arr[], int num, string str){
 81     for (int i = 0; i < num; i  ) {
 82         cout<< str << " #" << i   1 << " :" << arr[i] << "; ";
 83     }
 84     cout << endl;
 85 }
 86 
 87 输出结果:
 88 Before change:
 89  num1:12
 90  num2:15
 91 fnum1:10.5
 92 fnum2:5.5
 93 arr1 #1 :11; arr1 #2 :22; arr1 #3 :33; 
 94 arr2 #1 :100; arr2 #2 :200; arr2 #3 :300; 
 95 farr1 #1 :11.5; farr1 #2 :22.5; farr1 #3 :33.5; 
 96 farr2 #1 :100.1; farr2 #2 :200.2; farr2 #3 :300.4; 
 97 
 98 After change:
 99  num1:15
100  num2:12
101 fnum1:5.5
102 fnum2:10.5
103 arr1 #1 :100; arr1 #2 :200; arr1 #3 :300; 
104 arr2 #1 :11; arr2 #2 :22; arr2 #3 :33; 
105 farr1 #1 :100.1; farr1 #2 :200.2; farr1 #3 :300.4; 
106 farr2 #1 :11.5; farr2 #2 :22.5; farr2 #3 :33.5;

 

 

 

2、显示具体化

  (1)第三代具体化选择了下面的方法:

    *对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及他们的重载版本;

    *显示具体化的原型和定义应以template<>打头,并通过名称来指出类型;

    *具体化优先于常规模板,而非模板函数优先于具体化和常规模板。

  下面是一个用于交换job结构的非模板函数、模板函数和具体化的函数原型:

    void Swap(job &,job &);//非模板化函数

 

    template <typename T> //模板函数

    void Swap(T &, T &);

 

    template <>void  Swap<job>(job & ,job &);//显示具体化模板函数;

  

 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 typedef struct {
 6     int age;
 7     string name;
 8 } Person;
 9 
10 void Swap(int &, int &);
11 
12 template <typename T>
13 void Swap(T &, T &);
14 
15 template <>void Swap<Person>(Person &,Person &);
16 
17 void show(const int &);
18 
19 template <typename T>
20 void show(const T &);
21 
22 template<>void show<Person>(const Person &);
23 
24 int main(int argc, const char * argv[]) {
25   
26     int a1 = 12;
27     int a2 = 15;
28     
29     float f1 = 12.5;
30     float f2 = 10.5;
31     
32     Person p1{24, "MuPiaomiao"};
33     Person p2{25, "HongMeng"};
34     
35     Swap(a1, a2);
36     Swap(f1, f2);
37     Swap(p1, p2);
38     
39     show(a1);
40     show(f1);
41     show(p1);
42     
43     return 0;
44 }
45 void Swap(int & a, int & b){
46     a = a   b;
47     b = a - b;
48     a = a - b;
49     cout << "调用了Swap(int &, int &) n";
50 }
51 
52 template <typename T>
53 void Swap(T & a, T & b){
54     T c;
55     c = a;
56     a = b;
57     b = c;
58     cout << "调用了Swap(T &,T &)n";
59 }
60 
61 template <>void Swap<Person>(Person & a,Person & b){
62     Person onePer = a;
63     a.age = b.age;
64     b.age = onePer.age;
65     cout << "调用了Swap<Person>(Person &, Person & b)n";
66 }
67 
68 void show(const int & a){
69     cout << "调用了show(const int &):" << a << endl ;
70 }
71 
72 template <typename T>
73 void show(const T & a){
74     cout << "调用了show(const T &):" << a << endl;
75 }
76 
77 template<>void show<Person>(const Person & per){
78     cout << "调用了show<Person>(const Person &):" << per.name << ", " << per.age << endl;
79  }
80 
81 输出结果:
82 调用了Swap(int &, int &) 
83 调用了Swap(T &,T &)
84 调用了Swap<Person>(Person &, Person & b)
85 调用了show(const int &):15
86 调用了show(const T &):10.5
87 调用了show<Person>(const Person &):MuPiaomiao, 25

   3、实例化和具体化

  注意:在代码中包含函数模板本身并不会生成函数定义,它只是一个生成函数定义的方案。编译器使用模板为特定类型生成函数定义时,得到的是模板实例。

   如下模板:

1 template <typename T>
2 void Swap(T & a, T & b){
3     a =  a   b;
4     b = a - b;
5     a = a - b;
6 }

  调用:

1 int main(){
2   int num1 = 12;
3   int num2 = 15;
4   Swap(num1,num2);      
5   return 0;      
6 }

  说明:调用Swap(num1,num2)导致编译器生成Swap()的一个实例,该实例使用int类型。函数模板并非函数定义,但使用int的模板实例是函数定义。这种实例化方式被称为隐式实例化。编译器之所以知道要进行实例化,是因为函数调用提供了一个int类型参数。隐式实例化即,在函数调用时,编译器根据调用函数提供的类型参数生成函数模板实例。  

  显式实例化

    C 允许显式实例化,其语法是声明所需的类型——用<>指示类型,并在声明前加上template关键字。例如:

      template void Swap<int>(int &,int &);//显式实例化

    编译器看到上述声明后,将使用Swap()模板生成一个int类型的实例。即,该声明的意思是“使用Swap()模板生成一个int类型的定义”。

  显式具体化

    显式具体化使用下面两种等价的方式之一:

      template<> void Swap<int>(int &, int &);

      template<> void Swap(int &,int &);

    上述声明的含义是,“不要使用Swap()模板生成函数定义,而应使用专门为int类型显示地定义的函数定义”。这些原型必须有自己的定义。显式具体化声明在关键字template后面包含<>,而显式实例没有。

 

  显式实例化与显式具体化的区别:

    *在形式上:显式具体化在关键字template后面有<>,而显式实例化没有;

    *在含义上:显式实例化的含义是,让编译器使用模板生成一个在显式实例化声明中指出的类型的函数定义,原型的定义由编译器来定义。显式具体化的含义是,不要使用模板生成定义,而应使用专门为显式具体化声明中指出的类型所定义的函数,原型的定义需要自己来定义。

 

  说明:显式具体化,主要用在,某种特殊类型的功能定义与通用功能定义有所不同。例如,声明和定义了一个函数模板,其功能是在屏幕上打印,该函数模板用来打印常规类型的变量可以正常实现;但是在打印结构体或者类对象的时候,就需要重新定义函数的功能。

 

  隐式实例化、显式实例化和显式具体化统称为具体化。它们的相同之处在于,它们表示的都是使用具体类型的函数定义,而不是通用类型。

  引入显式实例化后,必须使用新的语法——在声明中使用前缀template和template<>,用以区分显示实例化和现实具体化。

 1 #include <iostream>
 2 #include <string>
 3 //显式具体化,可以理解成是典型化,即为某种不能通过函数模板通用定义实现函数功能的特殊类型(比如结构,类对象)单独地定义适合该类型的函数定义。
 4 using namespace std;
 5 typedef struct {
 6     int age;
 7     string name;
 8 }Person;
 9 
10 template <typename T> void change(T &, T &);//声明函数模板
11 template <> void change<Person>(Person &, Person &); //显式具体化,需要自己定义函数定义
12 
13 template <typename T> void show(const T &);
14 template <> void show<Person>(const Person &);
15 
16 int main(int argc, const char * argv[]) {
17     int num1 = 15;
18     int num2 = 13;
19     
20     float fnum1 = 22.5;
21     float fnum2 = 25.0;
22     
23     Person p1 {60,"木缥缈"};
24     Person p2 {25,"小红"};
25     
26     show(num1);
27     show<int>(num2);
28     
29     show(fnum1);
30     show<float>(fnum2);
31     
32     show(p1);
33     show(p2);
34     
35     change(num1, num2);
36     change<float>(fnum1,fnum2);//可以在程序中使用函数创建显示实例化
37     change(p1, p2);//编译器不会生成Person类型的函数定义,而是使用显式具体化定义的函数定义
38     
39     show(num1);
40     show<int>(num2);
41     
42     show(fnum1);
43     show<float>(fnum2);
44     
45     show(p1);
46     show(p2);
47     
48     return 0;
49 }
50 
51 template <typename T> void change(T & a, T & b){
52     a = a   b;
53     b = a - b;
54     a = a - b;
55 }
56 
57 template <> void change<Person>(Person &p1, Person &p2){
58     p1.age = p1.age   p2.age;
59     p2.age = p1.age - p2.age;
60     p1.age = p1.age - p2.age;
61     
62 }
63 
64 template <typename T> void show(const T & a){
65     cout << &a << " : " << a << endl;
66 }
67 template <> void show<Person>(const Person &p){
68     cout << &p <<" : " << "name--" << p.name << ", age--" << p.age <<";n";
69 }
70 
71 输出结果:
72 0x7fff5fbff7ac : 15
73 0x7fff5fbff7a8 : 13
74 0x7fff5fbff7a4 : 22.5
75 0x7fff5fbff7a0 : 25
76 0x7fff5fbff780 : name--木缥缈, age--60;
77 0x7fff5fbff760 : name--小红, age--25;
78 0x7fff5fbff7ac : 13
79 0x7fff5fbff7a8 : 15
80 0x7fff5fbff7a4 : 25
81 0x7fff5fbff7a0 : 22.5
82 0x7fff5fbff780 : name--木缥缈, age--25;
83 0x7fff5fbff760 : name--小红, age--60;

  4、编译器选择使用哪个函数版本

    对于函数重载、函数模板和函数模板重载,C 需要(且有)一个定义良好的策略,来决定为函数调用使用哪一个函数定义,尤其是有多个参数时。这个过程称为重载解析。其大致过程如下:

      *第一步:创建候选函数列表。其中包含与被调用函数名称相同的函数和模板函数;

      *第二步:使用候选函数列表创建可行函数列表。这些都是参数数目正确的函数,为此有一个隐式转换序列,其中包含实参类型与响应的形参类型完全匹配的情况。

      *第三步:确定是否有最佳的可行函数。如果有,则使用它,否则该函数调用会出错。

 

  5、模板函数的发展

   (1)decltype关键字

        decltype(x) y;

      含义:声明y,使y的类型与x的类型相同,其中x可以是表达式或函数调用等。例如:

        *  int num1 ;

           decltype(num1) num2;//作出这样的声明以后,num2的类型和num1的类型一样为int

        *  int a = 12;

          float b = 15.2;

          decltype(a b) c;//这样声明以后,c的类型将会是表达式类型转换后得到的值的类型,在这里转换后为float,因此c的类型为float。

        *  decltype(x y) sum = x y;//sum的类型为x+y的值的类型,并且用x + y的值初始化了sum。

      为确定类型,编译器必须遍历一个核对表。假定有如下声明:

            decltype(expression) var;

      则核对表的简化版如下:

         第一步:如果expression是一个没有用括号扩起来的标识符,则var的类型和该标识符的类型相同,包括const限定符:

            int num;

            const float num2;

            decltype(num) num3 ;//num3的类型为int

            decltype(num2) num4;//num4的类型为const float

        第二步:如果expression为函数调用,则var的类型和函数的返回值类型相同。

            long sum();

            decltype(sum()) num;//num的类型和函数sum()的返回值类型相同,在这里为long

         注意:在这里,并不会实际调用函数。编译器通过查看函数原型来获悉返回值类型,而无序实际调用函数。

       第三步:如果expression是一个左值,则var为指向其类型的引用。要进入第三步,expression必须是用括号扩起来的标识符。

            int num;

            decltype(num) num1;//num1的类型为int    

            decltype((num)) num2;//num2的类型为int &;

       第四步:如果前面的条件都不满足,则var的类型与expression的类型相同。

     注意:如果需要多次声明,可以结合decltype和typedef,例如:

          typedef decltype(num num2) theType;

          theType num3,num4;//num3和num4的类型和表达式的类型一样

  (2)另一种函数声明语法(C 11后置返回值类型)

    有一个相关的问题是decltype本身无法解决的。请看下面这个不完整的函数模板:

      template <typename T1, typename T2> 

      ?type? gt(T1 x, T2 y){

        ....

        return x y;

      }

     同样,无法预先知道x和y相加得到的类型;同时,也无法通过decltype(x y)来得到返回值的类型,因为x和y是局部变量,此时还没声明x和y。必须在声明后使用decltype,为此C 新增了一种声明和定义函数的语法——后置返回值类型。

      诸如以前的函数声明形式:

        typeName funcName(arguments);

      可以改成:

        auto funcName(arguments) -> typeName;

      其中,->typeName被称为后置返回类型;auto是一个占位符,表示后置返回类型提供的类型,这种方法也可以用于函数定义。例如,可以做出如下的声明:

        auto sum(int a, float b) -> double{

          /*函数体*/

        }

      通过结合使用这种语法和decltype,便可给gt()指定返回类型

        template <typename T1, typename T2>

        auto gt(T1 x, T2 y) -> decltype(x y){

          ...

          return x y;

        }

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 template <typename T1, typename T2>
 6 auto sum(T1 x, T2 y) ->decltype(x   y);
 7 
 8 int main(int argc, const char * argv[]) {
 9    
10     int num1 = 12;
11     long double num2 = 14.9;
12     decltype(sum(num1,num2)) num3 = sum(num1, num2);
13     cout << "sizeof num1:" << sizeof(num1) << endl;
14     cout << "sizeof num2:" << sizeof(num2) << endl;
15     cout << "sizeof num3:" << sizeof(num3) << endl;
16     return 0;
17 }
18 
19 template <typename T1, typename T2>
20 auto sum(T1 x, T2 y) ->decltype(x   y){
21     return x   y;
22 }
23 
24 输出结果:
25 sizeof num1:4
26 sizeof num2:16
27 sizeof num3:16

 

   

 

 

1无参函数:无参函数是指函数在定义和调用中均不带参数,调用和被调用函数之间不进行参数传递。

 

2有参函数:参数包括形式参数和实际参数,在函数定义和说明时的参数称之为形参,调用时给出的函数称之为实参。

 

2.2函数的定义

 

定义的一般形式:

 

类型说明符 函数名(类型名 形式参数1,类型名 形式参数2,.....)

 

{

 

函数体

 

}

 

函数名:是一个标示符,取名要求有意义、简短,同一源程序文件中不能重名。

 

类型说明符:值函数的返回值类型,使用void来指定函数无返回值,返回值类型可以是基本类型也可以是指针、结构体和用户自定义类型。

 

形式参数:定义函数时的参数称之为形式参数,参数列表说明了参数的类型和个数,一下两种方式都是正确的:

 

int max(int a,intb){

 

Return 0;

 

}

 

int max(a,b)

 

int a,b;{

 

Return 0;

 

}

 

函数体:即函数具体功能的实现。

 

调用函数判断素数例子代码:

 

 

#include "math.h" 

main() {  

  int n; 

  int flag; 

  printf("input n:n"); 

  scanf("%d",&n); 

  flag=prime(n);           /*函数调用*/ 

  if (flag)                 /*判断返回值*/ 

     printf("%d is prime.n"); 

  else  printf("%d is not prime.n"); 

int prime (int n){ 

  int m; 

  for (m=2;m<=sqrt(n);m ) 

      if (n%m==0) return  0; 

  return 1; 

3函数调用

 

3.1函数调用的一般形式:

 

函数名(参数列表);

本文由betway必威发布于编程开发,转载请注明出处:C语言学习笔记,对C语言的非面向对象特性增添

TAG标签: betway必威
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。