——二维数组,二维数组名取地址,二级指针

先给出结论,便于以后查阅:
首先给出几个定义:
typedef int (p1x4)[4];//定义数据类型,p1x4这种类型为指向含4个int元素的1维数组的指针
typedef int (
p3x4)[3][4];//定义数据类型,p3x4这种类型为指向含3x4个int元素的2维数组的指针
下面从一维数组说起:(定义:int a[4])
(1)一维数组名a是个地址,地址类型为:int *
(2)一维数组名取地址&a是个地址,地址类型同:int (*p)[4], 也即&a指向含4个int元素的一维数组


再看二维数组b[3][4],这个二维数组也可以可以看成一个含3个成员的一维数组,每一个成员含有4个int元素,依次,仿照一维数组的结论,有:
(1)b[0]是个一维数组名,也是个地址,地址类型为:int *
(2)&b[0]是个地址,地址类型同:int (p)[4], 也即&b[0]指向含4个int元素的一维数组

更进一步:
(3)b是个地址,地址类型同:int (
p)[4],也即b指向含4个int元素的一维数组
(4)&b是个地址,地址类型同:int (*p)[3][4],也即&b指向含3x4个int元素的2维数组


注意:尤其注意上面的(3),这条结论指出了:
二维数组名实际上是一维数组的地址!b==&b[0]
而b[0]又是b[0][0]的地址(类比一维数组中a是a[0]的地址),即b[0]==&b[0][0, 最后有b = &(&b[0][0])


总结:
① 数组名,是指向它的第一个一级成员的指针
② 数组名取地址,是指向整个数组的指针
PS:所谓一级成员,举个例子,int a[5],那么数组a的一级成员就是int型变量;int b[10][5],数组b的一级成员是int [5]的一维数组


例题:
int a[3];
int *b=a;
int *c=&a; //错误
int (*c)[3]=&a; //正确
int *d=&a[0];
int *e=&a[1];
int *f=&a[2];
        例子解析:     

       1. 定义一维数组a[3],在例子中,a、&a、&a[0]的值虽然都是一样,但是意义不一样。a代表a[3]这个数组,也是a[3]的起始地址。&a就是取a[3]的起始地址。&a[0]就是取数组第一个元素的地址。     

        2. 例子中,使用int *c=&a是错误的,因为a的数据类型是int (*)[3],而不是int *,所以必须以int (*c)[3]=&a。       

        3. 定义了int (*c)[3]=&a,可以使用(*c)[0]取得a[0],(*c)[1]取得a[1],(*c)[2]取得a[2]。



~字符串类型

C++中的字符串一般有以下四种类型,

  • string

  • char*

  • const char*
  • char[]

1)string

string是一个C++类库中的一个类,它位于名称空间std中,因此必须使用using编译指令或者std::string来引用它。它包含了对字符串的各种常用操作,它较char*的优势是内容可以动态拓展,以及对字符串操作的方便快捷,用+号进行字符串的连接是最常用的操作。

2)char*

char* 是指向字符串的指针(其实严格来说,它是指向字符串的首个字母),你可以让它指向一串常量字符串。

3)const char*

该声明指出,指针指向的是一个const char类型,即能通过当前的指针对字符串的内容作出修改

注意这里有两个概念:

  • char * const [指向字符的静态指针]
  • const char * [指向静态字符的指针]
前者const修饰的是指针,代表不能改变指针
后者const修饰的是char,代表字符不能改变,但是指针可以变,也就是说该指针可以指针其他的const char。
 

4)char[]

与char*与许多相同点,代表字符数组,可以对应一个字符串,如
char * a="string1";
char b[]="string2";
这里a是一个指向char变量的指针,b则是一个char数组(字符数组)

5)char[ ],char*二者的不同点

一,char*是变量,值可以改变, char[]是常量,值不能改变!
a是一个char型指针变量,其值(指向)可以改变;
b是一个char型数组的名字,也是该数组首元素的地址,是常量,其值不可以改变


二,char[]对应的内存区域总是可写,char*指向的区域有时可写,有时只读
char * a="string1";
char b[]="string2";
gets(a); //试图将读入的字符串保存到a指向的区域,运行崩溃! 
gets(b) //OK

解释: a指向的是一个字符串常量,即指向的内存区域只读;
b始终指向他所代表的数组在内存中的位置,始终可写!

注意,若改成这样gets(a)就合法了:

char * a="string1";
char b[]="string2";
a=b; //a,b指向同一个区域
gets(a) //OK
printf("%s",b) //会出现gets(a)时输入的结果

解释: a的值变成了是字符数组首地址,即&b[0],该地址指向的区域是char *或者说 char[8],习惯上称该类型为字符数组,其实也可以称之为“字符串变量”,区域可读可写。

总结:char *本身是一个字符指针变量,但是它既可以指向字符串常量,又可以指向字符串变量,指向的类型决定了对应的字符串能不能改变!



三,char * 和char[]的初始化操作有着根本区别:
测试代码:
char *a="Hello World"; 
char b[]="Hello World"; 
printf("%s, %d\n","Hello World", "Hello World"); 
printf("%s, %d %d\n", a, a,  &a);                           
printf("%s, %d %d\n", b, b,  &b);
结果:
Hello World,13457308
Hello World,13457308    2030316
Hello World,2030316 2030316
结果可见:尽管都对应了相同的字符串,但”Hellow World”的地址 和 a对应的地址相同,与b指向的地址有较大差异;&a 、&b都是在同一内存区域,且&b==b
根据c内存区域划分知识,我们知道,局部变量都创建在栈区,而常量都创建在文字常量区,显然,a、b都是栈区的变量,但是a指向了常量(字符串常量),b则指向了变量(字符数组),指向了自己(&b==b==&b[0])。
说明以下问题:

char * a=”string1”;是实现了3个操作:

  1. 声明一个char*变量(也就是声明了一个指向char的指针变量);
  2. 在内存中的文字常量区中开辟了一个空间存储字符串常量”string1”
  3. 返回这个区域的地址,作为值,赋给这个字符指针变量a
最终的结果:指针变量a指向了这一个字符串常量“string1”
(注意,如果这时候我们再执行:char * c=”string1”;则,c==a,实际上,只会执行上述步骤的1和3,因为这个常量已经在内存中创建)

char b[]=”string2”;则是实现了2个操作:

  1. 声明一个char 的数组,
  2. 为该数组“赋值”,即将”string2”的每一个字符分别赋值给数组的每一个元素

最终的结果:“数组的值”(注意不是b的值)等于”string2”,而不是b指向一个字符串常量

实际上, char * a=”string1”; 的写法是不规范的!
因为a指向了即字符常量,一旦strcpy(a,”string2”)就糟糕了,试图向只读的内存区域写入,程序会崩溃的!尽管VS下的编译器不会警告,但如果你使用了语法严谨的Linux下的C编译器GCC,或者在windows下使用MinGW编译器就会得到警告。
所以,我们还是应当按照”类型相同赋值”的原则来写代码:

const char * a="string1";

保证意外赋值语句不会通过编译


另外,关于char*和char[]在函数参数中还有一个特殊之处,运行下面的代码
void fun1 ( char *p1,  char p2[] ) {
 printf("%s %d %d\n",p1,p1,&p1);
 printf("%s %d %d\n",p2,p2,&p2);
p2="asdf"; //通过! 说明p2不是常量! 
printf("%s %d %d\n",p2,p2,&p2);
}
void main(){
char a[]="Hello";
fun1(a,a);
}
运行结果:
Hello 3471628 3471332
Hello 3471628 3471336
asdf 10704764 3471336
结果出乎意料!上面结果表明p2这时候根本就是一个指针变量!
结论是:作为函数的形式参数,两种写法完全等效的!都是指针变量

const char*与char[]的区别:

const char * a=”string1”
char b[]=”string2”;
二者的区别在于:
  1. a是const char 类型, b是char const类型
    ( 或者理解为 (const char)xx 和 char (const xx) )

  2. a是一个指针变量,a的值(指向)是可以改变的,但a只能指向(字符串)常量,指向的区域的内容不可改变;

  3. b是一个指针常量,b的值(指向)不能变;但b指向的目标(数组b在内存中的区域)的内容是可变的

  4. 作为函数的声明的参数的时候,char []是被当做char *来处理的!两种形参声明写法完全等效!



——数组作为函数的参数

~传值与传址

《C和指针》函数的参数一节(7.3节)指出,C函数的所有参数均以“传值调用”方式进行传递,这意味着函数将获得参数值的一份拷贝。这样函数可以放心地修改这个拷贝值,而不必担心会修改调用程序实际传递给它的参数。

如果被传递的参数是一个数组名,由于数组名的值是一个指向数组第一个元素的指针,因此实际传递给函数的是指向数组起始位置的指针的一份拷贝,该指针同样指向数组起始位置。在函数内部对指针形参进行间接访问操作,实际访问的是原数组的元素。

对于传值和传址:
1. 传递给函数的标量参数是传值调用的。
2. 传递给函数的数组参数在行为上就像它们是通过传址调用的那样。


~一维数组作为函数的参数

声明数组参数可以采用2种方式
int func(char  *str);    //推荐使用
int func(char  str[ ]);
由于数组名作为参数传递给函数时,函数实际接收到的是一个指针,因此第一种声明是更为准确的。在函数内部sizeof(str)的值将会是字符指针的长度,而不是数组的长度。
编译器同样接受第二种声明形式。数组形参无需写明它的元素数目,是因为函数并不为数组参数分配内存空间,形参只是一个指针。因此数组形参可以与任何长度的数组匹配。如果函数需要知道数组的长度,它必须作为一个显式的参数传递给函数。


~多维数组作为函数的参数

一维数组作为函数形参,在声明时可以把它写成数组的形式,也可以把它写成指针形式。但对于多维数组,只有第一维可以进行如此选择,这里的关键在于编译器必须知道第2个及后面各维的长度才能对各下标进行求值,因此在原型中必须声明这些维的长度。
可以使用下面2种方式
void func(int  (*array)[10]);
void func(int  array[ ][10]);

写成下面的原型是不正确的:
void func(int **array);
这个例子把array声明为一个指向整型指针的指针,它和指向整型数组的指针并不是一回事。


~数组做参数小结

一级指针做函数参数:
int array(char buf[60]);会退化为指针
int array(char buf[])
int array(char * buf)
二级指针做函数参数
int array2(char array[10][30])//10无作用 30确定其步长
int array(char array[][30])
int array(char (*array)[30])//数组指针的
 
二维数组可以看做是一维数组
二维数组中的每个元素是一维数组
二维数组参数中第一维的参数可以省略

void f(int a[5])     ====》void f(int a[]);      ===》 void f(int* a);
void g(int a[3][3])====》 void g(int a[][3]); ====》 void g(int (*a)[3]); 



——new int() 和 new int[ ]的区别

1. new int[] 是创建一个int型数组,数组大小是在[]中指定,
    例如:int * p = new int[10]; //p执行一个长度为10的int数组。
2. new int()是创建一个int型数,并且用()括号中的数据进行初始化,
    例如:int *p = new int(10); // p指向一个值为10的int数。



——array对象(数组)

C++11标准库 - array

std::array是原生数组的封装,它存放于栈上且大小固定,性能也与之相同。在原生数组的基础上,它添加了范围检查,以及其它的STL的相应特性,比如复制、交换、迭代器、查询大小。
避免了动态数组new和delete的使用,内存自动管理。而且,执行效率比vector高。
按照C++11的规范,应该抛弃原生数组,所有使用原生数组的地方,都应按需换成vector或者array。对于固定个数的一组值,可以考虑使用std::array。

<array>头文件中定义:
template< 
    class T, 
    std::size_t N 
> struct array;

初始化
std::array属于简单对象,没有构造函数,也没有私有或保护成员,这就意味着它不会自动初始化。如果不对其初始化而直接获取其中的值,读出来的是野值

一维数组声明
array<int, 5> arr1 = {1, 2, 3, 4, 5};
array<int, 5> arr1{ {1, 2, 3, 4, 5} };
访问:
int n1 = arr1[0];
int n2 = arr1.at(0); 

二维数组声明
array< array<int, 3> , 2 > arr2d = {11,12,13,21,22,23};
访问:
int n3 = arr2d[0][0];

数组遍历
for(size_t i = 0; i < arr2d.size(); ++i)
{
	for(size_t j= 0; j < arr2d[i].size(); ++j)
	{
		cout << arr2d[i][j] << '\t';
	}
	cout << endl;
}
可以看到,二位数组实际上也是一个一维数组,其元素是二维数组

访问元素:
可以用operator[]at()对元素随机访问,两者都带范围检查。也可以直接使用迭代器,或者范围for语句。

下面的例子对数组中的十个元素求出最大值,最小值和平均值:
#include <iostream>
#include <array>

using namespace std;

int main()
{
    array<int, 10> arr = {17, 68, 87, 48, 1, 76, 90, 73, 95, 13};

    int minVal = INT_MAX;
    int maxVal = INT_MIN;
    int sum = 0;

    cout << "数组的元素:";

    for (int elem : arr)
    {
        cout << elem << ' ';

        minVal = minVal < elem ? minVal : elem;
        maxVal = maxVal > elem ? maxVal : elem;
        sum += elem;
    }

    cout << endl
        << "最大值:" << maxVal << endl
        << "最小值:" << minVal << endl
        << "平均值:" << double(sum) / arr.size() << endl;

    return 0;
}
结果:
数组的元素:17 68 87 48 1 76 90 73 95 13
最大值:95
最小值:1
平均值:56.8

















——引用变量


01)c和c++使用&来指示变量的地址。c++赋予了&另一个含义,将其用来声明引用
int rats;
int & rodents = rats; //将rodents作为rats的别名 int &表示是指向int的引用,其中&不是地址运算符
上述声明允许将rats和rodents呼唤,他们指向相同的内存单元,将rodets加1将会影响两个变量
更准确的说rodents++操作将一个有两个名称的变量加1

02)必须在声明引用时将其初始化 如:
int rats;
int & rodents;
rodents = rats; //这样做是错误的

03)引用和指针是有区别的
int rets;
int* parts = &rats; //声明并初始化一个指针parts,该指针指向rats
int & rodents = rats; //声明并初始化一个引用rodents,使rodents成为rats的别名
这样表达式rodents和*parts都可以和rats互换,而表达式&rodents和parts也都可以和&rats互换

04)引用更接近于const指针,必须在创建时进行初始化,一旦与某个变量关联起来,就一直效忠于它
也就是说:int & rodents = rats;
实际上是该代码的伪装表示:int* const pr = &rats;
其中引用扮演的角色与表达式*pr相同

05)int rats = 101;
int* pt = &rats; //声明并初始化一个指针parts,该指针指向rats
int & rodents = *pt; //由于*pt就等价于rats,所以该句的意思就是声明并初始化一个引用rodents,使rodents成为rats的别名
int business = 50;
pt = &business; //pt改为指向business,但是rodents还是指向rats

 1 #include <iostream>
 2 
 3 using namespace std;  
 4 
 5 int main()
 6 {
 7     int rats = 101;
 8     int & rodents = rats;  //声明并初始化一个引用rodents,使rodents成为rats的别名
 9 
10     cout << "value of rats is: " << rats << "; " << "Address of rats is: " << &rats << endl;
11     cout << "value of rodents is: " << rodents << "; " << "Address of rodents is: " << &rodents << endl;
12 
13     int business = 50;
14     rodents = business;  //等价于rats = business,执行完此句之后,rats和rodents的值都会改变为50,且二者的地址相同,但二者的地址和business是不一样的
15     cout << "value of business is: " << business << "; " << "Address of business is: " << &business << endl;
16     cout << "value of rats is: " << rats << "; " << "Address of rats is: " << &rats << endl;
17     cout << "value of rodents is: " << rodents << "; " << "Address of rodents is: " << &rodents << endl;
18 
19     system("pause");
20     return 0;
21 }


~将引用变量作为函数参数 


1 //使用引用变量、指针、和普通变量作为函数参数交换形参的值
 2 #include <iostream>
 3 
 4 void swapr(int & a, int & b);  //声明参数为引用变量的函数
 5 void swapp(int* a, int* b);  //声明参数为指针的函数
 6 void swapv(int a, int b);  //声明参数为普通变量的函数
 7 
 8 int main()
 9 {
10     using namespace std;
11 
12     int wallet1 = 200;
13     int wallet2 = 300;
14 
15     cout << "原始数据为:wallet1 = " << wallet1 << endl;
16     cout << "原始数据为:wallet2 = " << wallet2 << endl;
17 
18     swapr(wallet1, wallet2);  //调用参数为引用变量的函数
19     cout << "调用参数为引用变量的函数:wallet1 = " << wallet1 << endl;
20     cout << "调用参数为引用变量的函数:wallet2 = " << wallet2 << endl;
21 
22     swapp(&wallet1, &wallet2);  //调用参数为指针的函数
23     cout << "调用参数为指针的函数:wallet1 = " << wallet1 << endl;
24     cout << "调用参数为指针的函数:wallet2 = " << wallet2 << endl;
25 
26     swapv(wallet1, wallet2);  //调用参数为普通变量的函数
27     cout << "调用参数为普通变量的函数:wallet1 = " << wallet1 << endl;
28     cout << "调用参数为普通变量的函数:wallet2 = " << wallet2 << endl;
29 
30     system("pause");
31     return 0;
32 }
33 
34 void swapr(int & a, int & b)  //该子函数修改的是主函数中的值,假如在主函数中调用该子函数时候传入了主函数中的值
35 {
36     int temp = a;
37     a = b;
38     b = temp;
39 }
40 void swapp(int* a, int* b)  ////该子函数修改的是主函数中的值,假如在主函数中调用该子函数时候传入了主函数中的值
41 {
42     int temp = *a;
43     *a = *b;
44     *b = temp;
45 }
46 void swapv(int a, int b)  //该子函数只是复制了传入该子函数的值给a、b,并交换了a和b的值,并没有交换主函数中的wallet1和wallet2的值
47 {
48     int temp = a;
49     a = b;
50     b = temp;
51 }


~使用引用作为形参,会改变主函数中的实参的值 


01)左值:左值参数是可被引用的数据对象,例如:变量、数组元素、结构成员、引用和接触引用的指针都是左值
     非左值:包括字面常量(用引号括起的字符串除外,它们由地址表示)和含有多项式的表达式

02)应尽可能在声明函数中的形参时候使用const,原因如下
     I 使用const可以避免无意中修改数据
     II 使用const能使函数能够处理const和非const实参,否则子函数就只能接收非const实参(假如子函数形参类型不是const)

 1 #include <iostream>
 2 
 3 double cube(double a);  //声明一个普通函数
 4 double refcube(double &ra);  //声明一个形参为引用的函数
 5 
 6 int main()
 7 {
 8     using namespace std;
 9     
10     double x = 3.0;  //定义常规变量
11 
12     cout << cube(x) << " = cube of " << x << endl;  //调用普通函数,此时x的值不会改变
13 
14     cout << refcube(x) << " = cube of " << x << endl;  //但是这样将refcube(x)和x一起打印的话还是x的值是不变的,执行完这一句之后x = ra的值即27
15 
16     double z = refcube(x);  //调用引用函数,此时x的值会随着子函数中ra的值的改变而改变 此时x=27
17     cout <<z<< " = cube of " << x << endl;  //打印19683= cube of 19683
18 
19     /*如果ra是一个变量的别名(即引用),则实参应该是变量,而不应该是表达式*/
20     //double z1 = refcube(x+10);  //不合法
21     //double z1 = refcube(10);  //不合法
22     double yo[3] = { 2.2,3.3,4.5 };  //声明一个数组
23 
24     double z1 = refcube(yo[2]);  //合法  数组元素
25     cout << "refcube(yo[2]) = " << z1 << endl;
26 
27     /* 有关左值的概念 */
28     double side = 3.0;
29     double* pd = &side;  //创建指向side的指针pd
30     double & rd = side;  //创建引用变量,rd即等价于side
31     long edge = 5L;  //创建长型变量,注意要在数字最后加上字母L
32     double lens[4] = { 2.0,5.0,10.0,12.0 };  //创建数组
33 
34     double c1 = refcube(side);  //ra is side
35     double c2 = refcube(lens[2]);  //ra is lens[2]
36     double c3 = refcube(rd);  //ra is rd is side
37     double c4 = refcube(*pd); //ra is *pd is side
38 
39     double c5 = refcube(edge);  //不合法,因为ra是double类型的,而edge是long类型的
40     double c6 = refcube(7.0); //7.0是非左值,不合法
41     double c7 = refcube(side + 7.0);  //side+7.0是包含多项式的表达式,是非左值,不合法
42     //c++遇到上述三种类型,就会创建类型正确的匿名变量,将函数调用的参数的值传递给改匿名变量,并让参数来引用改变量
43 
44     
45 
46     system("pause");
47     return 0;
48 }
49 
50 double cube(double a)
51 {
52     /*a = a * a*a;*/
53     a = a*a * a;
54 
55     return a;
56 }
57 double refcube(double &ra)
58 {
59     //ra = ra * ra*ra;
60     ra = ra* ra * ra;  //也可以这么写  ra *= ra*ra;
61 
62     return ra;
63 }


~将引用应用于结构 


01) 使用结构引用参数的方式于使用基本便令引用方式相同,只需在结构参数使用引用运算符&即可

02) 假如有如下结构:
struct free_throws
      {
         std::string name;
            string made;
            int attemps;
            float percent;
      };
      则可以这样声明子函数,在子函数中将指向改结构的引用,作为参数
      void set_pc(free_throws & ft); //可以更改结构中的参数
      void display(const free_throws & ft); //加上const则不能更改结构中的参数

 1 #include <iostream>
 2 #include <string>
 3 
 4 
 5 struct free_throws
 6 {
 7     std::string name;  //新建一个string型字符串变量name
 8     int  made;
 9     int attemps;
10     float percent;
11 };
12 
13 void display(const free_throws & ft);  //声明一个函数,形参为指向结构free_throws的引用ft,但是不可以通过ft来改变结构中的值,此处的ft为const引用参数
14 void set_pc(free_throws & ft); //声明一个函数,形参为指向结构free_throws的引用ft
15 free_throws & accumulate(free_throws & target, const free_throws & source);  //声明一个返回值为free_throws结构引用的函数accumulate,形参为两个指向结构的引用
16 
17 int main()
18 {
19     //初始化结构变量
20     free_throws one = { "Ifelsa Branch",13,14 }; //没有被赋值的就默认为0,比如以下均没有对percent赋值,则percent均为0
21     free_throws two = { "Andor Knott",10,16 };
22     free_throws three = { "Mininie Max",7,9 };
23     free_throws four = { "Whily Looper",5,9 };
24     free_throws five = { "Long Long ago",6,14 };
25     free_throws team = { "Throwgoods",0,0 };
26     free_throws dup;  //定义结构dup,没有初始化
27 
28     //01简单调用形参结构的子函数
29     set_pc(one);  //调用子函数,参数为指向结构的引用  此句为填充one结构中percent的值
30     display(one);  //调用子函数,参数为指向结构的引用  此句为显示整个one结构中的值
31 
32 
33     //02调用返回值为结构的子函数,但是这个返回值并没有使用,而是用子函数中的引用去修改主函数中结构的值
34     accumulate(team, one);  //这一句的确是会返回一个结构,但是由于该函数的形参为引用,所以传入的team结构中的值也会被改变
35     //其中在accumulate子函数中,第二个引用为conts类型,对应one,只是 用以下one结构中的值,不会改变one中的值,如果在子函数中要求改变,会报错
36     //在accumulate子函数中,第二个参数为非const类型,故可以在子函数中更改team中的值,且在子函数中修改target的值就是修改team的值

37     display(team);
38 
39     //03使用accumulate子函数的返回值,作为另外一子函数display的实参,accumulate子函数的返回值为引用,进一步说是结构引用
40     display(accumulate(team, two));              //使用accumulate(team, two)的返回值(team)作为display的实参
41     accumulate(accumulate(team, three),four);
42     display(team);
43 
44     //04将返回值赋给一个没有初始化的结构 dup
45     dup = accumulate(team, five);
46     std::cout << "display(team): \n";
47     display(team);
48 
49     std::cout <<"Displaying dup after assignment: '\n'";
50     display(dup);
51 
52     set_pc(four);  //重新设置结构four中的percent的值
53 
54     system("pause");
55     return 0;
56 }
57 
58 void display(const free_throws & ft)
59 {
60     using std::cout;
61     cout << "Name: " << ft.name << '\n';        //其中'\n'表示换行,用endl也是可以的
62     cout << " Made: " << ft.made << '\t';
63     cout << "Attemps: " << ft.attemps << '\t';  //'\t'表示制表符
64     cout << "Percent: " << ft.percent << '\n';
65 
66 }
67 void set_pc(free_throws & ft)  //由于没有对ft加限定符const,所以该子函数允许修改原结构中的变量的值
68 {
69     if (ft.attemps != 0)
70         ft.percent = 100.0f*float(ft.made) / float(ft.attemps);      //100.0f表示100为float型
71     else
72         ft.percent = 0;        //percent的值就被修改,而不用返回percent的值到主函数,是直接修改的主函数中的结构原型中的结构参数,而不是修改的副本
73 }
74 free_throws & accumulate(free_throws & target, const free_throws & source)
75 {
76     target.attemps += source.attemps;      //赋值操作
77     target.made += source.made;       //赋值操作
78     set_pc(target);          //在子函数中调用子函数,重新对指向结构的引用target赋值
79     return target;        //返回值为结构引用 或者说返回值为target,假如传入的是team,那么target就是team的引用,即target和team是等价的,返回target就是返回team了
80 }
01)对于srt_pc(one); 必须使用按引用传递参数,不可使用按值传递,如果使用按值传递,则修改不了结构one中的percent的值

02)另一种方法是使用指针参数并传递地址 *****
set_pc(&one)
......
void set_pc(free_throws* pt)
{
if (pt->attemps != 0)
           pt->percent = 100.0f*float(pt->made) / float(pt->attemps); //100.0f表示100为float型
         else
           pt->percent = 0;
}
//注意:由于ft是指向结构的指针,所以只可以使用间接成员运算符->来访问结构中的成员

03)display(one); 由于只是显示结构中的内容,所以使用了一个const引用参数


~为何要使用引用

01)传统返回机制于安宅传递函数参数类似,计算关键字return后的表达式,并将结构返回给调用函数
从概念上说这个只被复制到一个临时位置,而调用程序将使用这个值

02)dup = acuumulate(team,five); //注意返回值就是team,因为传入的时候target是team的引用,那么target和team是等价的
如果accumulate返回的是一个结构,而不是指向结构的引用,将把整个结构复制到一个临时位置,再将
这个拷贝复制给dup。但在返回值为引用时,将直接把team复制到dup,效率会更高。



——空引用null调用方法且不报空指针异常

~什么是null

null既不是对象也不是一种类型,它仅是一种特殊的值,你可以将其赋予任何引用类型,你也可以将null转化成任何类型。

String str = null;// null can be assigned to String
Integer itr = null;// you can assign null to Integer also
Double dbl = null; // null can also be assigned to Double
 
String myStr = (String) null;// null can be type cast to String
Integer myItr = (Integer) null;// it can also be type casted to Integer
Double myDbl = (Double) null;// yes it's possible, no erro

进入正题:

下面的代码,通过为null的引用调用静态方法,且并未产生异常。
public class Why {
  public static void test() {
        System.out.println("Passed");
  }

  public static void main(String[] args) {
        Why NULL = null;
        NULL.test();
  }
}
test() 是一个静态方法,调用静态方法不需要创建实例对象。静态成员应该通过类型来访问,上面对test()方法通常应该写为Why.test();Java中可以通过对象引用表达式来访问一个静态成员,但这样通常会引起误解,因为这不是访问静态成员的常见形式。
hy aNull = null; 
aNull.test(); // 实际开发中不要这样写
// 调用 Why.test(), 不会抛出 NullPointerException

当通过一个对象引用访问静态成员时,访问只与所声明的引用类型相关。即: 
1. 所引用对象是否为null无关紧要,因为访问静态方法不需要实例对象。 
2. 如果引用不为null,运行时对象类型也无关紧要,因为静态调用不会导致动态调用分派。而是与类相关。

对于 2. 的示例:
class T {
    public static void p() {
        System.out.println("p() in T");
    }
}

class T1 extends T {
    public static void p() {
        System.out.println("p() in T1");
    }
}

 
public class Main {
    public static void main(String[] args) {
        T t = new T1();
        t.p();
    }
}
最后访问静态方法,建议使用标准写法来写,这样不容易引起误解,也便于走读维护。