——二维数组,二维数组名取地址,二级指针
typedef int (p1x4)[4];//定义数据类型,p1x4这种类型为指向含4个int元素的1维数组的指针
typedef int (p3x4)[3][4];//定义数据类型,p3x4这种类型为指向含3x4个int元素的2维数组的指针
(1)一维数组名a是个地址,地址类型为:int *
(2)一维数组名取地址&a是个地址,地址类型同:int (*p)[4], 也即&a指向含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
2)char*
3)const char*
该声明指出,指针指向的是一个const char类型,即不能通过当前的指针对字符串的内容作出修改
注意这里有两个概念:
- char * const [指向字符的静态指针]
- const char * [指向静态字符的指针]
后者const修饰的是char,代表字符不能改变,但是指针可以变,也就是说该指针可以指针其他的const char。
4)char[]
char * a="string1"; char b[]="string2";这里a是一个指向char变量的指针,b则是一个char数组(字符数组)
5)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个操作:
- 声明一个char*变量(也就是声明了一个指向char的指针变量);
- 在内存中的文字常量区中开辟了一个空间存储字符串常量”string1”
- 返回这个区域的地址,作为值,赋给这个字符指针变量a
(注意,如果这时候我们再执行:char * c=”string1”;则,c==a,实际上,只会执行上述步骤的1和3,因为这个常量已经在内存中创建)
char b[]=”string2”;则是实现了2个操作:
- 声明一个char 的数组,
- 为该数组“赋值”,即将”string2”的每一个字符分别赋值给数组的每一个元素
最终的结果:“数组的值”(注意不是b的值)等于”string2”,而不是b指向一个字符串常量
实际上, char * a=”string1”; 的写法是不规范的!
因为a指向了即字符常量,一旦strcpy(a,”string2”)就糟糕了,试图向只读的内存区域写入,程序会崩溃的!尽管VS下的编译器不会警告,但如果你使用了语法严谨的Linux下的C编译器GCC,或者在windows下使用MinGW编译器就会得到警告。
所以,我们还是应当按照”类型相同赋值”的原则来写代码:
const char * a="string1";
保证意外赋值语句不会通过编译
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
结论是:作为函数的形式参数,两种写法完全等效的!都是指针变量
const char*与char[]的区别:
char b[]=”string2”;
二者的区别在于:
-
a是const char 类型, b是char const类型
( 或者理解为 (const char)xx 和 char (const xx) ) -
a是一个指针变量,a的值(指向)是可以改变的,但a只能指向(字符串)常量,指向的区域的内容不可改变;
-
b是一个指针常量,b的值(指向)不能变;但b指向的目标(数组b在内存中的区域)的内容是可变的
- 作为函数的声明的参数的时候,char []是被当做char *来处理的!两种形参声明写法完全等效!
——数组作为函数的参数
~传值与传址
如果被传递的参数是一个数组名,由于数组名的值是一个指向数组第一个元素的指针,因此实际传递给函数的是指向数组起始位置的指针的一份拷贝,该指针同样指向数组起始位置。在函数内部对指针形参进行间接访问操作,实际访问的是原数组的元素。
1. 传递给函数的标量参数是传值调用的。
2. 传递给函数的数组参数在行为上就像它们是通过传址调用的那样。
~一维数组作为函数的参数
int func(char *str); //推荐使用
int func(char str[ ]);
~多维数组作为函数的参数
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[ ]的区别
2. new int()是创建一个int型数,并且用()括号中的数据进行初始化,
——array对象(数组)
C++11标准库 - 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; }可以看到,二位数组实际上也是一个一维数组,其元素是二维数组
访问元素:
#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
——引用变量
int rats;
int & rodents = rats; //将rodents作为rats的别名 int &表示是指向int的引用,其中&不是地址运算符
上述声明允许将rats和rodents呼唤,他们指向相同的内存单元,将rodets加1将会影响两个变量
更准确的说rodents++操作将一个有两个名称的变量加1
int rats;
int & rodents;
rodents = rats; //这样做是错误的
int rets;
int* parts = &rats; //声明并初始化一个指针parts,该指针指向rats
int & rodents = rats; //声明并初始化一个引用rodents,使rodents成为rats的别名
这样表达式rodents和*parts都可以和rats互换,而表达式&rodents和parts也都可以和&rats互换
也就是说:int & rodents = rats;
实际上是该代码的伪装表示:int* const pr = &rats;
其中引用扮演的角色与表达式*pr相同
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 }
~使用引用作为形参,会改变主函数中的实参的值
非左值:包括字面常量(用引号括起的字符串除外,它们由地址表示)和含有多项式的表达式
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 }
~将引用应用于结构
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引用参数
~为何要使用引用
从概念上说这个只被复制到一个临时位置,而调用程序将使用这个值
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
进入正题:
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,运行时对象类型也无关紧要,因为静态调用不会导致动态调用分派。而是与类相关。
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(); } }最后访问静态方法,建议使用标准写法来写,这样不容易引起误解,也便于走读维护。