Chinaunix首页 | 论坛 | 博客
  • 博客访问: 82329
  • 博文数量: 21
  • 博客积分: 371
  • 博客等级: 一等列兵
  • 技术积分: 225
  • 用 户 组: 普通用户
  • 注册时间: 2012-11-15 21:32
文章分类

全部博文(21)

文章存档

2013年(5)

2012年(16)

我的朋友

分类: C/C++

2012-12-13 22:23:48

1.如果使用引用形参的唯一目的是避免赋值实参,则将形参定义为const引用.

 

2.应该将不需要修改的引用形参定义为const引用.普通的非const引用形参在使用时不太灵活,这样的形参既不能用const对象初始化,也不能用字面值或产生右值的表达式参数实例化.

 

3.通常函数不应该有vector或者其他的标准库容器类型的形参.C++倾向通过传递指向容器中需要处理的元素的迭代器来传递容器.

 

4.当编译器检查数组形参关联的实参时,它只会检查实参是不是指针、指针的类型和数组元素的类型是否匹配,而不会检查数组的长度.

 

5.如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身.这种情况下,数组大小成为形参和实参类型的一部分.编译器检查数组实参的大小与形参的大小是否匹配.

 

6.确保函数的操作不超出数组实参边界的技巧:

1).在数组本身存放一个标志来检测数组的结束.(C语言的字符串就是利用这种方式)

2).传递指向数组的第一个和最后一个元素的下一位的指针.(类似于迭代器)

3).显示传递表示数组大小的实参

 

7.函数的返回值用于初始化在调用函数处创建的临时对象(在求解表达式时,如果需要一个地方存储其运算结果,编译器会创建一个没有命名的对象).函数返回值类型的处理方法:

       1).如果是非引用,在调用函数的地方会将函数的返回值复制给临时对象,当函数返回非引用类型时,其返回值既可以是局部变量又可以是求解表达式的结果

2).返回值为引用时,没有复制返回值,返回的是对象的本身

注意:千万不要返回局部变量的引用

 

8.代码:

char &get_value(string &str,string::size_type ix)

{

       return str[ix];

}

void main()

{

       string s("a value");

       cout<<s<<endl;

       get_value(s,0) = 'A';

       cout<<s<<endl;

}

 

9.自动变量相当于局部变量.内联函数应该在头文件中定义,这一点与其他函数一致.这样可以确保在调用函数是所使用的定义是相同的,并且保证在调用点该函数的定义对编译器可见.

 

10.在类的成员函数中声明const,在成员函数中,不能修改对象本身的成员变量.另外,const对象、指针const对象的指针或引用只能被const成员函数调用

11.如果没有为一个类显式定义任何构造函数,编译器将会为这个类自动生成默认的构造函数.另外,在全局定义的成员变量或静态定义的成员变量,默认为0.(即与非类的变量一致)

 

12.const引用的形参的函数与有非const引用形参的函数是不同的.

 

13.重载函数的匹配情况:

1).编译器找不到与实参最佳匹配的函数,并生成调用该函数的代码.

2).找不到形参与函数调用的实参匹配的函数;在这种情况下,编译器将给出编译错误信息

3).找到多个与实参匹配的函数,在这种情况下,编译器也将产生编译错误.(即所谓的二义性).

 

14.重载确定的三个步骤:

1).候选函数:确定该调用所考虑的重载函数集;

2)选择可行函数:从候选函数中选择一个或多个函数,他们能够用该调用中指定的实参来调用.

可行函数的满足条件:

①  函数的形参个数与该调用的实参的个数相同

②  ②每个实参的类型必须与对应形参的类型匹配,或者可被隐式转换为对应的形参类型

3)寻找最佳匹配

4).含有多个形参的重载确定

15.指向函数的指针,向其他指针一样,函数指针也指向某个特定的类型,函数类型由其返回的类型即形参确定,与函数名无关.

使用typedef来简化其定义,:

typedef  bool (*pFun)(参数列表)//这种形式在dll中常常使用.

该定义表示pFun指针,它指向返回bool类型并带有参数 的指针

允许将形参定义为函数类型,但函数的返回类型则必须是指向函数的指针,而不能是函数.


7.1 函数的定义

     函数是完整的一个逻辑代码块。 函数接收一些参数并在函数体内做处理。函数可以返回运算结果也可能不返回任何内容。

     C++有两类函数 无返回值的void函数 和 有返回值函数。

7.2 参数传递

     Ø非引用形参

       参数是以复制形式传递的,函数内部对参数修改不一会对调用函数的实参产生影响。看下面代码

点击(此处)折叠或打开

  1. void funct(int i , string s)
  2. {
  3.     i = 3;
  4.     s = "new";
  5. }

  6. int it = 2;
  7. string str = "old";

  8. cout << it << str << endl; // 输出: 2 old

  9. funct(it, str);

  10. cout << it << str << endl; // 输出还是: 2 old

       调用函数funct时传递的是实参的拷贝,虽然在函数内更改了参数值但不影响外部实参。

       需要注意的是指针。在函数内对指针参数本身(指针的指向地址)的更改同样不会影响调用实参。不过对指针指向的内容更改却是有影响的。

点击(此处)折叠或打开

  1. void funct(int *i)
  2. {
  3.     *i = 3; // 对指向内容做更改
  4.     i = 0; // 对指针做更改
  5. }

  6. int it = 2;
  7. string *itpr = &it;
  8. cout << itpr << *itpr << endl; // 输出: 000ffxx0 2
  9. funct(itpr);
  10. cout << itpr << *itpr << endl; // 输出: 000ffxx0 3

      函数对指针本身做的更改(指向地址)不会反应到外部实参。 指向内容做更改对外部实参是有影响的,实际上两个指针指向同一内容,任意一个指针所做的更改对其他指针都有反应。

      函数体内两个表达式顺序执行对结果有很大影响。若先改变指针指向再改变指向内容值,实际上就表示两个指针指向了不同对象,这时修改指向内容对外部实参没有丝毫影响。

      有些时候我们希望传递的参数不允许修改,在参数前增加 const 关键字就可以实现。

  1. void func(const int i)
  2. {
  3.      i = 3;// error, can not change the value
  4. }

      指针比较特殊。指针操作有两个含义:指针指向更改和指向内容更改。这个常量修饰符实际修饰的是指向内容,指向地址还是可以更改如下代码:

  1. void function(const int *i)
  2. {
  3.       *i = 2 ; // 错误,不允许更改指向内容
  4.       i = 0 ; // 允许更爱指针指向地址
  5.  }

    Ø引用形参

      非引用传参方式有个很大的弊端,当需要传递一个很大的参数时拷贝参数副本开销比较大。如果我们要求函数对参数修改能反映到外部的实参时非引用方式也无能为力。

      这个时候可以使用引用类型传参。

点击(此处)折叠或打开

  1. void funct(int &i , const int &j)
  2. {
  3.     i = 0 ;
  4.     j = 0 ; // 不允许更改
  5.  }

  6. int it = 2;
  7. int jt = 1;
  8. cout << it << jt << endl; // 输出: 2 1

  9. funct(it, jt);
  10. cout << it << jt << endl; // 输出: 0 1

      it以引用方式传递,函数内部修改 i 实际上就是在修改 it 本身。 如果限制函数更改可以增加 const 修饰。j 参数经过修饰后内部不允许做修改。

      对于指针来说指针引用(*&parname形参在函数体内指向更改或者指向内容更改都会反映到外部实参:

点击(此处)折叠或打开

  1. void funct(int *&p)
  2. {
  3.     p = 1100ffxx ; // 修改指向
  4.     *p = 6 ; // 修改指向内容 注意此时修改的是新地址的内容值

  5.  }

  6. int it = 2;
  7. int *pr = &it;
  8. cout << pr << *pr << endl; // 输出: fff110xx 2

  9. funct(pr);
  10. cout << pr << *pr << endl; // 输出: 1100ffxx 6

    函数形参可传递实参一般分为 字面值常量, 普通常量,变量。不同情况下可传递的情况不同   

非引用形参:
void funct(int p)  
{
}
int i(1);
const int j(1);
funct(15); // 传递字面值

funct(i); // 传递变量
funct(j); // 传递常量

也可以const限定参数 void funct(const int p) 三类参数传递已然正确,唯一的区别是限定后函数体内不允许更改参数 p


点击(此处)折叠或打开

  1. 非const引用形参:
  2. void funct(int &p)
  3. {
  4. }
  5. int i(1);
  6. const int j(1);
  7. funct(3); // 不允许传递字面值
  8. funct(i); // 传递变量
  9. funct(j); // 不允许,函数内部可以修改参数值,而参数是常量时修改操作是是非法的

  10. const引用形参:
  11. void funct(const int &p)
  12. {
  13. }
  14. int i(1);
  15. const int j(1);
  16. funct(1); // 传递字面值
  17. funct(i); // 传递变量
  18. funct(j); // 传递常量
    应该掌握每种情况下可传递的实参类型。

    对于容器参数如果按拷贝传递性能会大大降低。可以传递引用。 但实际操作中传递容器的迭代器会更加方便。迭代器类似于数组指针。   

    数组也可以作为形参,不过数组比较特殊因为数组不允许赋值,所以数组新参会被转换为对应指针。

    void funct(int *i) ; // 推荐写法,表明参数是一个指向数组的指针

    void funct(int[]) ;

    void funct(int[10]) ;

    这三种方式是一样的,实际等价于 void funct(int *i)

    第三种形参定义设置了数组大小,但是实际调用中可以传递大小不等于10的数组,因为形参定义的数组大小会被忽略所以在函数中的操作不能依赖形参定义的大小

   如果想严格匹配实参和形参数组大小可使用 引用数组,定义方式如下:

    void funct(int (&arr)[10]) ; // 表示是一个数组引用,且数组大小是10 。 圆括号必须因为下标操作具有更高优先级

    void funct(int &arr[10]) ;  // 错误的语法

   

    函数操作数组不但可以如上传递还能可以传递指针,数组大小等参数类型

点击(此处)折叠或打开

  1. // 传递第一位和末端哨兵位
  2. void funct(int *bej, int *end)
  3. {
  4.     for(int *i = bej; i != end; i++)
  5.     {
  6.         cout << *i << endl;
  7.     }
  8. }

  9. int it[]{1,9,7,0,1};
  10. funct(it, it + 5); // 注意:末端哨兵位是最后一位的后一位



  11. // 传递第一位和数组大小
  12. void funct(int *bej, size_t len)
  13. {
  14.     for(size_t i = 0; i < len; i++)
  15.     {
  16.         cout << bej[i] << endl; // 数组指针下标操作等价于 *(beg + i)
  17.     }
  18. }

  19. size_t len = 5;
  20. int it[len]{1,9,7,0,1};
  21. funct(it, len);

7.3 函数返回值

    函数可以没有返回值(void函数)也可以有返回值。同参数一样函数可返回多种类型。

    Ø返回非引用类型--------这种情况和形参类似,返回的是对象副本。

int funct(int a)
{
    return 1 ; // 返回字面值副本

    return a ; // 返回参数副本
    int b(1) ;
    return b ; // 返回局部变量副本

}

    Ø返回引用类型-----返回引用表示可以对返回值做赋值操作。

// 返回普通引用
int &funct(int &a) 
{
    return a;
}
int i(1);
int &j = funct(i); // 也可以这样操作 funct(i) = 3
j = 3;

cout << i << j << endl //
 输出 3  3 j 是 i 的引用


// 可以返回const引用 

const int &funct(int &a) 
{
    return a;
}
int i(1);
int &j = funct(i); // 错误,不能用const引用初始化非const引用

const int &j = funct(i);
j = 3// 错误,不能为常量赋值

    千万记住:不管任何时候都不允许用const引用初始化非const引用(或者说不允许让非 const 引用指向一个const对象)

    返回值遵循一个安全约束,即 不允许返回局部对象的引用或指针

// 返回引用
int &funct(int a, int &b) 
{
    int c = 1
    return c; // 标准局部对象 不允许返回


    return a;  // 传参时拷贝的实参,实际值只有在函数内有效,不允许返回
    return b;  // 通过引用形参 传递的是外部实参的引用,可以传递
}
// 返回指针

int *funct(int a, int *b) 
{
    int c = 1
    return *c; // 标准局部对象 不允许返回


    return *a;  // 传参时拷贝的实参,实际值只有在函数内有效, 不允许返回
    return b;   // 传参时拷贝的实参,虽然指针是局部对象但他指向的值是外部数据,可以传递
}

    总之判断返回是否是局部变量可以看返回值的实际值是作用范围否只在函数体内。

7.4 函数声明

    函数调用时还未定义则需要声明。函数声明和变量声明类似可查看相关内容。

    函数形参可以定义默认值,调用函数时如果不提供参数值则使用默认值

void funct(int a = 1string b = "val"string c = " "
{
}

funct()        // 等同于 funct(1,"val", " ")

funct(3)       // 等同于 funct(3,"val", " ")
funct(3,"new"// 等同于 funct(3, "new", " ")

7.5 局部对象

    局部对象的作用范围限制在函数内,函数执行完毕局部变量会被系统自动销毁。

    不过静态局部对象不会销毁,所有静态对象一旦创建就会一直存在直到应用程序关闭。

    静态对象初始化只会执行一次:

void funct() 
{
    static int a(0); // 只会执行一次,第二次执行到代码处会检查静态变量 a 是否存在若存在则跳过

    a++;
    cout << a << endl;    

// 函数到此执行完毕并清理局部对象,但不会清理静态对象,所以a依然存在,值为最后操作结果。 a虽然不会被清理但只限在定义的函数体内访问


funct() ; // 输出 1
funct() ; // 输出 2
funct() ; // 输出 3

7.6 内联函数

    内联函数是指编译时将调用函数的地方用实际函数体语句替代的一类函数,函数定义时返回值前加上 inline 就表示函数内联  

    inline bool funct(int a, int`b)
    {  
        return a > b;
    }

    bool c = funct(a, b) ; // 编译时实际会被替换为 bool c = (a > b);

    内联函数有较好的性能,因为函数在调用时系统刚要分配栈空间,内联函数会直接展开代码,所以不会有栈空间分配步骤 (内联函数体内数据需要的空间会在调用它的函数执行时一并分配)。

    内联函数的特点决定了如果修改函数则所有用到内联函数的地方都要重新编译。

7.7 类的成员函数

    类成员函数和普通该函数没有本质区别。

    类成员函数包含一个隐藏参数 this 指针。它指向成员函数所属的当前类对象。

    类成员函数名后面可以用const修饰,其作用是表示 this 指针指向常量,也就是说不允许修改当前类的任何成员

class myclass
{
    public:
        int a ;

    void funct1() const
    {
        a = 1// 错误,等价于 this->a = 1 由于函数后面const修饰 this 指针指向了常量所以无法修改

    }

    void funct2()
    {
        a = 1// 允许,等价于 this->a = 1

    }
}

    类成员函数比较多,比如普通成员函数,构造函数,拷贝函数,析构函数等等,会在后面详解。

7.8 函数重载

    函数重载是指返回类型相同,函数名相同但参数不完全相同的多个函数。函数调用时会根据传递的参数类型和个数寻找最合适的重载函数。

    不但参数类型和个数可以作为重载依据,当形参数是引用或指针时 const 可用作重载依据。

点击(此处)折叠或打开

  1. // 参数类型不同
  2. void funct(int a);
  3. void funct(string a);
  4.  
  5. // 参数个数不同
  6. void funct();
  7. void funct(int a);

  8. // 引用形参时 是否是const
  9. void funct(int &a);
  10. void funct(const int &a);
  11. int a;
  12. const int b;
  13. funct(a); // 调用 void funct(int &a);
  14. funct(b); // 调用 void funct(const int &a)
  15.  
  16. // 指针参数时 是否是const
  17. void funct(int *a);
  18. void funct(const int *a);
  19. int a;
  20. int *b = &a;
  21. const int *c = &a;
  22. funct(b); // 调用 void funct(int *a);
  23. funct(c); // 调用 void funct(const int *a)
  24.  
  25. void funct(int a); //不能和 void funct(int &a); 或 void funct(const int &a); 做重载,实参是一个变量时调用函数有歧义

    通常来说函数以参数类型不同或参数数量来重载。

7.9 指向函数的指针

    指针不但可以指向内置类型,类类型,数组,还可以指向函数。函数指针的最大作用是将函数作为一种类型传递。

    定义语法是:

    void (*pr)(int i, string s) ; // 定义了一个函数指针,指向的函数无返回值,并且有两个参数一个是int类型另外一个string类型

    pr = funct ; // 为定义的指针赋值(funct是函数名称)

    pr(1,"str") ;   //  通过指针调用函数(不用解引)

    可以用 typedef 可简化定义,将定义一个变为定义一类

    typedef void (*pr)(int i, string s) ; // 定义了一类函数指针,指向的函数无返回值,并且有两个参数一个是int类型另外一个string类型

    pr f1 = funct ; // 定义类型变量

    f1(1,"str") ;    // 通过指针调用函数

    在这两种方式中,指针都可以赋0值,表示还未指向任何函数。

    最后我们看看实际中函数指针的应用

typedef void (*pr)(int a);

// 形参是函数指针 返回函数指针

pr getfunction(pr f,int`a)
{
    f(a);  // 执行

    return f; //  返回函数指针
}

pr f1 = getfunction(funct, 1);
f1(2);

    定义参数时用了typedef的类型pr,如果不使用则定义语句相当复杂。

    对于有重载的函数,定义的指针一定要有对应的重载版本。 初始化指针变量时系统也会查找对应的重载函数

void fun(int a);
void fun(string a);

typedef void (*pr)(string); // 定义了string形参版的函数指针


pr p = fun; // 定义函数指针变量,系统会用 void fun(string) 版函数来初始化p
阅读(1238) | 评论(0) | 转发(0) |
0

上一篇:第六章 语句

下一篇:c++缓冲区

给主人留下些什么吧!~~