lwflourish

哪怕自己是一个菜鸟,也要努力使自己展翅高飞
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

引用&&指针&&返回值&&定义声明&&生命周期&&const

Posted on 2014-12-10 16:48  lwflourish  阅读(260)  评论(0编辑  收藏  举报

1. 首先了解一下声明和定义的区别:

    声明,其实就是描述一个元素是有什么构成的;
    定义,其实就是在内存中划分出一个区域且用符号关联起来;
    变量和对象不加extern永远是定义,类中的除外。 函数只有函数头是声明,有函数体的是定义。 类永远只是声明。类成员函数的函数体是定义
    函数的声明和定义的不同点在于函数的声明不包含函数体,仅仅是fact();

2. 其次了解一下局部对象的生命周期

    局部变量
i.    形参和函数体内定义的变量统称局部变量
ii.    局部变量转换成为全局变量,则使用static关键字就能实现;
iii.    局部变量的生存周期,在函数调用时被创建,在函数结束的时候就销毁,例:形参
iv.    有两种方式可以将函数体内值得改变传回主函数,一种是将形参设置为引用的形参,其实是实参和形参绑定在一块儿,因而值可以回传,另外一种是通过指针形参,改变实参传递过来地址的值,去改变函数体内的值。

3. 引用

A.    可能出现的错误情况:
Int  ival =1024;
Int  &refVal;  //引用必须进行初始化,
改正: int &refVal = ival;
//因为无法令引用重新绑定到另外一个对象,因此引用必须进行初始化;
引用并非对象,相反的,它只是为一个已经存在的对象所起的另外一个名字;
B.    情况2:
Int  &refVal1 =10; // 错误:引用类型的初始值必须是一个对象,不能是字面值;
double dval = 3.14;
int &refVal =dval; //错误,此处引用类型的初始值必须是int类型的对象;
C.    总结: 引用初始值必须是一个对象,不能是字面值;引用必须进行初始化;引用类型和绑定的类型必须一致。切记,哪怕类型之间可以相互转换,也不可以,必须类型一致;

4. const &

A.    const 用法
i.    定义一种变量,它的值不能被改变。
ii.    好处:用一个变量来表示缓冲区的大小,使用变量的好处是当我们觉得缓冲区大小不再合适时,很容易对其进行调整,但是我们又要随时警惕防止程序一不小心改变这个值。因而用const
iii.    const 必须进行初始化;
iv.    引用的好处:避免拷贝,可以直接比较;使用引用形参返回额外信息,主要是为了改善一个函数只能返回一个值得情况,这样函数可以返回多值。
v.    补充:顶层const是指指针本身是个常量,指针所指的内容是可以改变的,当形参有顶层const时,顶层const可以忽略掉,传给他常量对象或者非常量对象都是可以的,因为传入形参的本身就是局部变量,不会改变实参的值。
vi.    数组引用形参
B.    总结:
常量引用也必须进行初始化,并且不能通过该对象别名去修改已知非常量的值,绑定的值可以是常量也可以是非量,和引用有一点不同
double dval = 3.14; const int &ri =dval;//当类型不同时,转换时可以进行的.
//常量引用的好处之一避免拷贝对象~
bool
isShorter(const string &s1, const string &s2) { return s1.size() < s2.size(); }//避免拷贝对象

//常量引用的好处之二___可以使函数返回多个值

//返回s中c第一次出现的位置索引
//引用形参occurs负责统计c出现的总次数

string::size_type find_char(const string &s, char c, string::size_type &occurs)
{
auto ret = s.size(); //第一次出现的位置
occurs = 0;
for (decltype(ret) i = 0; i != s.size(); ++i)
{
if (s[i] == c)
{
if (ret == s.size())
ret = i; //记录c第一次出现的位置
++occurs;//将出现的次数+1
}
}
return ret; //出现次数通过occurs隐式传回
}

int main()
{
string s = { "Hello world, welcome to beijing !" };
decltype(s.size()) ctr = 0;
auto index = find_char(s, 'o', ctr);
cout << "string S has " << ctr << " times o" << endl;
}

5. 返回值为&

A.    关于返回值是如何返回的:返回的值用于初始化调用点的一个临时量,该临时两就是函数调用的结果。
B.    不要返回局部对象的引用或指针,局部对象会在函数调用结束的时候释放内存;
C.    引用返回的是左值;

//返回值为常饮用出现的错误之一

const string &manip()
{
string ret;//以某种方式改变一下ret
if (!ret.empty())
return ret; //错误:返回的是对局部变量的引用
else
return "Empty"; //错误:"Empty"是一个局部临时量
}

//  有的返回值也是局部变量,但是不会提示错误~~~

1>e:\c程序\finalconst\finalconst\finalconst.cpp(10): warning C4172: 返回局部变量或临时变量的地址
1>e:\c程序\finalconst\finalconst\finalconst.cpp(12): warning C4172: 返回局部变量或临时变量的地址

6. 数组形参

A.    数组的性质:不允许拷贝数组,使用数组时通常会将其转换成指针;
因而函数传递数组时,实际上传递的是指向数组首元素的指针。
B.    首先需要看一下三种等价的形参是数组的形式
void  print(const int*);
void print (const int[]);//可以看出来,函数的意图是作用于一个数组
//因而 void print(const int *arr[])==void print (const int **arr)
void print (const int[10]); //这里的维度表示我们期望数组含有多少元素实际不一定
这里每个函数的唯一形参都是const int * 类型,这是一个底端const

exe1:当形参为内置类型,不是混合类型(如指针、引用)

void reset(int ival)
{
ival = 3;
cout << ival << endl;
}
int main()
{
int ival1 = 5;
reset(ival1);
cout << ival1 << endl;
}

//输出值为3,5

exe2:当形参为指针类型的时候

void reset(int *ival)
{
*ival = 3;
cout << *ival <<" "<< ival << endl;//当传递的值为指针的时候,可以在函数体内

//通过指针改变指对象的值,并且该值被传回主函数,间接地改变主函数中所指地址中的值;

int ival2 = 4;
ival = &ival2;//改变函数传入的实参的值,也就是改变传入地址的值,仅仅是局部变量的拷贝,

//并不能使实参的值改变,如果注释掉*ival = 3;该函数调用不会改变主函数的任意值

}
int main()
{
int ival1 = 5;
reset(&ival1);
cout << ival1 <<" "<<&ival1<< endl;
}

输出值为:3,002AFB04

        3,002AFB04

exe:当形参为引用类型的时候

void reset(int &ival)
{
ival = 3;
cout << ival << " " << &ival<< endl;
}
int main()
{
int ival1 = 5;
reset(ival1);
cout << ival1 << " " << &ival1 << endl;
}

输出值为:3,002AFB48

         3,002AFB48

exe:当形参为内置类型时,在函数体内定义的变量是局部变量,但是仍然可以将值赋给主函数中的某个变量~~,这是值传递~~

int fact(int val)
{
int ret = 1;
while (val > 1)
ret *= val--;
return ret;
}

int main()
{
int j = fact(5);
cout << "5! is " << j << endl;
return 0;
}

exe:当形参是未知个数,但是类型相同的时候,可以用类initializer_list

void error_msg(initializer_list<string> i1)
{
for (auto beg = i1.begin(); beg != i1.end(); ++beg)
cout << *beg << " ";
cout << endl;
}
int main()
{ 
string expected;
string actual;
if (expected != actual)
error_msg({ "functional", expected, actual });
else
error_msg({ "functional", "okay" });
return 0;
}

 

最后当指针出现的各种情况的总结:

 1 #include <iostream>
 2 #include <string>
 3 #include<vector>
 4 using namespace std;
 5 
 6 
 7 int g_val = 8;
 8 //数组的数组
 9 void  pointer(int *ival)
10 {
11     *ival = 10;
12     cout << *ival << " " << ival << endl;
13     //在此处应该是把实参的值改为10,并且地址应该相同
14 }
15 void  pointer1(const int *ival)
16 {
17     cout << *(ival + 2) << endl;    
18     //输入的是数组的首地址,该地址的值不能改变,但是指针能向下移动
19 }
20 void pointer2(int *arr[])
21 {
22     cout <<**arr<<" "<< *arr << " " << arr << endl;
23 }
24 void pointer3(int(*arr)[4])//等价于void pointer3(int(arr[])[4])
25 {
26     for (size_t i = 0; i < 4; i++)
27     {
28         cout << (*arr)[i] <<" "<<arr[i]<< endl;//对指针数组的使用~
29     }
30 }
31 int *pointer4()//有意义的为int * const pointer4()
32 {
33     
34     return &g_val;//可以在类的使用中返回该值的地址,可以用于修改该对象的值;
35 }
36  
37 
38 int main()
39 {
40     int val = 4;
41     int val1[4] = { 0, 1, 2, 3 };
42     int val2[2][4] = { 0, 1, 2, 3, 4, 5, 6, 7 };
43     int *val3 = &val;
44     //多维数组初始化-----
45     //还可以初始化{{0,1,2},{3,4,5}}
46 
47     //test void  pointer(int *ival)
48     pointer(&val);
49     cout << val << " " << &val << endl;
50     pointer(val1);
51     cout << val1[0] << " " << val1 << endl;
52     //test void  pointer1(const int *ival)
53     pointer1(val1);
54 
55     //test void pointer2(int **arr)
56     pointer2(&val3);
57     //void pointer3(int(*arr)[4])
58     pointer3((val2+1));
59     //test int  *pointer4()
60     int *val4=pointer4();
61     cout << val4 << " " << *val4 << endl;
62     *val4 = 90;
63     cout << val4 << " " << *val4 << endl;
64     return 0;
65 }

实验结果:

 

 

参考:

  函数返回值是否使用引用类型的问题:理解引用、返回值
  1. http://blog.csdn.net/sxhelijian/article/details/7466540  

      理解一般指针类型

  1. http://www.cnblogs.com/dzry/archive/2011/05/12/2044835.html