十二. 指针

● 基本概念

位系统下为4字节(8位十六进制数),在64位系统下为8字节(16位十六进制数)

进制表示的, 内存地址不占用内存空间

指针本身是一种数据类型, 它可以指向int, char, float double等不同类型的变量, 仔细揣摩下图:

的时候(在CPU为32位的情况下),要根据指针的类型来前进相应字节数。比如int类型的指针前进四个字节,char型的指针前进一个字节,void类型指针前进一个字节……

位十六进制数占多少字节,比如0x12345678这是多少字节?

位十六进制数占4字节。这也是指针变量的占用4个字节的原因。

注意如下英语表达:

int *p;

p is the address of an int

(dereference p is an integer)

int v;

p = &v;

p stores the address of v

int x, y, *p;

p = &x; /* p gets the address of x */

y = *p; /* y gets the value point to by p*/

y = *(&x); /* same as y = x*/

C Pointer is used to allocate memory dynamically i.e. at run time.

//定义指针变量的几种形式:

//形式1:

int a = 10; //定义一个的int型变量a,a赋值为10

int *p;

p = &a; //定义一个int型指针变量p,p指向int型变量a并赋值为(be set to the value of)int型变量a的地址, 即&a; 这一语句表示的是对指针进行初始化

//形式2:

int a = 10;

int *p = &a; //同上,也就是说int *p等价于p

//形式3:

int *p, i = 10, j;    //如果是int *p, *i表示定义两个指针变量p和i

p = &i; // 以上是定义一个指针变量

j = *p; // 把p所指的内存单元,即变量"i"的内容(整数10)赋予变量j;*p代表p所指的变量i, 其内容是数据

// 上述的定义和语句等价于:

int *p; // 此时,p还是一个野指针

int i = 10;

int j;

p = &i; // 此时,p不再是一个野指针

j = *p;

//判断正误:

int *p;

*p = &a; //语法错误,因为int *p表示定义一个指针变量;*p代表p所指的变量a的具体数据

//—————————————————————————

int *p;

p = 10; //语法错误,因为不可以把一个数赋值给一个指针变量

//—————————————————————————

int *p = 10; // 语法错误,int *p后面只能是地址

//—————————————————————————

int *p = a; // 这里分两种情况:如果a是一个变量,那么语法错误;如果我们先定义了一个名为a的数组,例如:int a[10];,那么该语句合法,因为一个数组名就是一个指针常量(指针常量就是一个地址值),这里"常量指针"是指"指针本身是常量,而非指针指向的对象是常量

//—————————————————————————

char *p = "world"; // 语法正确,因为字符串"world"就相当于一个数组,指针变量存储的是字符'w'的地址。

//—————————————————————————

int **pp = &p; //语法正确,表示取指针地址--定义一个int型指针变量pp, pp指向指针变量p并赋值为指针变量p的地址

#include <stdio.h>

,表示这个main函数停止,而执行下面一个main函数

{

int a = 10;

int *p = &a;

int b = *p; //定义一个的int型变量b;"*指针变量"是引用指针变量的形式,其含义是引用指针变量所指向的值

printf("%d\n", b);

return 0;

}

// 结果为10

int main()

{

int a = 10;    //也可以是int a; 此时变量a没有初始化, 里面是垃圾值

int *p = &a;

*p = 20; //通过指针变量p间接地修改它指向的变量的值

printf("%d\n", a);

return 0;

}

● 二级指针/二重指针/指向指针的指针

三重及以上的指针统称为多重指针。

int **p, *s, k = 20;

s = &k;

p = &s;

// *s代表存储单元k,*p代表存储单元s,因此**p也代表存储单元k;p→s→k(20)

例如:

int **pp;

定义了一个二级指针变量,等效于下列定义方式:

typedef int * P;

P *p;

二级指针常用来指向一个指针数组。

例①:

int a[2][3]={1,2,3,4,5,6}; //声明并初始化二维数组

int *p_a[3]; //声明整型指针数组

p_a[0]=a[0]; //初始化指针数组元素

p_a[1]=a[1];

int **pp;

pp=p_a;

注意:在使用二级指针时经常容易犯两类错误。

(1)类型不匹配

例如:

pp=a; //错误,因为pp是一个int**型变量,a是一个int[2][3]型的地址

pp=&a; //错误,pp是一个int**型变量,&a是一个(*)int[2][3]型的地址

pp=&a[0]; //错误,pp是一个int**型变量,&a[0]是一个(*)int[3]型的地址

(2)指针没有逐级初始化

例如:

int i=3;

int **p2;

*p2=&i;

**p2=5;

cout<<**p2;

虽然上述程序段编译、连接均没有错误,但运行时出错。其原因在于int **p2;只是定义了一个指针变量,变量中的内容(地址)是一个无意义的地址,而*p2=&i是对无意义的内存单元赋值,这样是错误与危险的。正确的作法是从第一级指针开始,逐级初始化。

逐级初始化多级指针

例:

int i=3;

int **p2;

int *p1;

p1=&i; //初始化一级指针

p2=&p1; //初始化一级指针

**p2=5; //通过指针给变量i赋值

cout<<**p2; //结果为5

上述两个二级指针在内存中的分布下图所示。

● 指针的简单例子

#include <iostream.h>

#include <stdio.h>

int main()

{

int a=1;

printf("&a=%p\n",&a);

int *p=&a;    //输出变量a的地址

printf("p=%p\n",p);    //同上

printf("p=%x\n",p);    //同上

printf("*p=%d\n",*p);    //*p相当于它指向的整型变量a, 输出变量a的值

printf("&p=%p\n",&p);    //输出指针变量p的地址

int **pp=&p;        //二级指针, 相当于int *(*pp)=&p;(指针运算符是自右向左结合的)

printf("pp=%p\n",pp);    //输出指针变量p的地址

printf("*pp=%p\n",*pp);    //输出指针变量p的内容, *pp相当于(它指向的指针变量 np)指向(vp)的(relative clause)整型变量的地址

printf("**pp=%d\n",**pp);    //输出指针变量pp最终指向的变量a的值

printf("&pp=%p\n",&pp);        //输出指针变量pp的值

return 0;

}

● &和*两个运算符

&----address-of operator (取地址运算符)( (reference)):to initialize the pointer variable with the address of a variable that you've already declared

*----①pointer operator (指针运算符)( dereference): to declare that the variable after it is a unsign-int-type variable which stores the address of another variable

②indirection operator (间接访问运算符) : to access the value of the variable that the pointer is pointing to

单目运算符"*"必须出现在运算对象的左边,其运算对象是存放地址的指针变量或地址

单目运算符"&"必须出现在运算对象的左边,其运算对象是任何已经被声明的变量(包括在指针变量)

&*和*&的区别

/*

int a;

int *p=a;

&*p和*&a的区别: &和*的优先级相同,结合性为自右向左, 因此:

① &*p先进行*p运算, 再进行&*p运算, *p相当于它p指向的变量a, 所以 &*p相当于取变量a的地址;

② *&a先进行&a运算, 再进行*&a运算, &a相当于p, 所以*&a相当于变量a

*/

#include<stdio.h>

main()

{

long i;

long *p;

printf("please input the number:\n");

scanf("%ld",&i);

p=&i;

printf("the result1 is: %p\n",&*p);                     /*输出变量i的地址*/

printf("the result2 is: %p\n",&i);                     /*输出变量i的地址*/

}

 

● 有关*p

*p: ① 作为左值, 相当于它指向的变量, 向指针变量p所指的变量赋值;

② 作为右值, 相当于它指向的变量的值, 从指针变量p所指的变量取值

如果*p 前面有int, char等数据类型, 那就一起构成了指向int型变量的指针,指向char型变量的指针等. (相比整型指针变量/字符型指针变量更标准的读法)

#include <iostream.h>

int main()

{

int i=1,j;

int *p=&i;

*p=2;

j=*p;

cout<<i<<endl;

cout<<j<<endl;

return 0;

}

#include<stdio.h>

main()

{

long i;

long *p;

printf("please input the number:\n");

scanf("%ld",&i);

p=&i;

printf("the result1 is: %ld\n",*&i);                 /*输出变量i的值*/

printf("the result2 is: %ld\n",i);                    /*输入变量i的值*/

printf("the result3 is: %ld\n",*p);                 /*使用指针形式输出I的值*/

}

● 野指针, 垂悬指针/迷途指针, 空指针

① 野指针 (wild pointer) : 定以后没有初始化的指针, 例如:

int *p;

或者没有指向任何有效地址的指针也叫野指针, 例如:

int *p=10;

② 悬垂指针/迷途指针 (Dangling pointer): 当所指向的对空间/对象被释放或者收回,但是对该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址,此情况下该指针便称悬垂指针(也叫迷途指针); 此时, 应该将该指针赋值为NULL。

③ 空指针(NULL pointer)

强制转化为(void *)类型的指针), 所以程序在编译时将NULL替换成0; 0表示指向地址为0的单元(这个单元一般是不能使用的).

也就是说, 空指针事实上指向了内存的零地址,但是操作系统并没有使用零地址附近的空间,这样做是为了使指针指向一个已知的地方防止成为野指针. 例如:

int *pointer = NULL;

④ 空类型指针(void pointer)

定义一个指针变量,但不指定它指向具体哪种数据类型。不同类型的指针不能互相赋值, 例如:

char *p1="helloworld";

int *p2=p1;    //错误

但是, 空类型指针可以赋值给任何类型的指针, 例如:

void *p1="helloworld";

int *p2=p1;    //不推荐的做法, 推荐的做法是进行指针类型的强制转换, 例如:

char *p1="helloworld";

int *p2=p1;

int *p2=(int *)p1;

然而, 指针的强制转换没有意义, 因为我们只是改变了一个变量的地址, 不能强制转换指针指向的变量的类型

另外, 不允许对void型指针做加减法运算.

● 常量指针/指针常量

常量指针(指向常量的指针变量)&指针常量(常量性质的指针 / 指针常量指向变量)

关键: *是一个单目运算符,它的运算对象在其右边(不运算其左边的对象)

常量(的)指针 constant pointer(指针指向的是常量, 指向的不可变, 但它本身可变)

const char *p;        //从右向左读, * 读作a pointer to

p is a pointer to const char;

等价于:

char const * p;     //同上因为C++里面没有const*的运算符,所以const只能属于前面的类型。

//C++标准规定,const关键字放在类型或变量名之前等价的。

const int n=5; //same as below

int const m=10;

  • 数组名就是常量指针:

int a[10];

int *p=a;

a++;        //不合法, 因为a是指针p指向的常量, 因为a是地址值, 当然是常量

p++;

其实这就像我们经常看到的char *p="ABC", 指针p指向的也是字符A的地址

指针(型)常量(指向的是变量, 指向的int变量可变, 但它本身不可变)

char *const p;

p is a const pointer to char

△似乎下面这个声明本身就是错的, 编译器不通过

指针(型)常量(如果指向的是常量, 指向的不可变, 它本身也不可变)

char *const *p;

p is a const pointer to a const char

/*看变量声明的时候应该从右至左看,以如下为例:

char *const *p

个符号const,它修饰的是*p,*p表示p指向的内容,所以,p指向的内容是常量,下一个符号是*,这就表示该常量为指针,然后是char,就表示指向的内容是char*/

口诀:

const(*号)左边放,我是指针变量指向常量;

const(*号)右边放,我是指针常量指向变量;

const(*号)两边放,我是指针常量指向常量;

指针变量能改指向,指针常量不能转向!

要是全都变成常量,锁死了,我不能转向,你也甭想变样!

//常量指针的案例

#include<stdio.h>

int main()

{

int a=1, b=2;

int const *p=&a;

//*p=3;

//printf("%d\n",a);    //出错, 提示"l-value specifies const object"

p=&b;        //将b的地址赋给p

return 0;

}

_____________________________________________________

//指针常量的案例

#include<stdio.h>

int main()

{

int a=1, b=2;

int *const p=&a;

*p=3;

printf("%d\n",a);    //a的值变为3

printf("%d\n",*p);    //结果为3

//p=&b;

//printf("%d\n",*p);    //出错,因为指针p的值是个常量,不可改变,编译器会提示l-value specifies const object

return 0;

}

// 如果是    char *const p= "hello";*p='b'; 编译通过, 但会运行出错, 因为"hello"是字符串常量

// 如果是    char *const p= 'h';*p='b'; 编译不通过, 提示: cannot convert from 'const char' to 'char *const (字符指针常量)'

________________________________________________________

#include<stdio.h>

int main()

{

int a=1, b=2;

int *const *p=&a;        //出错, 提示cannot convert from 'int *' to 'int *const * '

//*p=3;

//printf("%d\n",a);        //出错

//p=&b;

//printf("%d\n",*p);    //出错

return 0;

}

● char p[]="abc123ABC"; 以及char *p="abc123ABC";

  • char p[]="abc123ABC"; 以及char *p="abc123ABC";的区别:

const在前,定义为常量指针;*在前,定义为指针常量。

  • const 在前,定义为常量指针;*在前,定义为指针常量

char p[]是一个数组,分配了内存, "abc123ABC" 复制到了该内存里面,这个内存可读可写, 保存在栈空间数组里

char *p是一个指针,没有分配内存(除了给这个指针变量本身分配的四个字节), 指针p指向的是字符串常量"abc123ABC", 其地址是第一个字符a的地址(也可以只指向一个字符), 保存在静态数据区. 另外, 用p=这样的赋值是可以的,也就是a指向了另外的地址。

※char *p 在C++03中不推荐使用(deprecated), 而应该写成const char *p = "abc123ABC ", 它等同于char const * p= "abc123ABC ", 又等价于:

char *p;

p="abc123ABC";

  • 总之,死记:字符串常量代表的是一个地址值, 字符串常量和数组一样,它也是当成指针来对待的,它的值就是字符串首个字符的地址.
  • 另外, 除了char buffer[20]= "hello ";和strcpy(p, "hello ");这两个情况外,程序中出现的字符串都应该被视为字符串常量.

#include <stdio.h>

int main()

{

char *p = "abc";

printf("%p\n", p);

printf("%p\n", "abc");

printf("%p\n", &(p[0]));    //p[0]代表字符'a'

printf("%p\n", &(p[1]));    //p[1]代表字符'b'

printf("%p\n", &(p[2]));    //p[2]代表字符'c'

printf("%c\n", p[3]);    //p[3]代表转义字符空字符'\0'(NULL), 其ASCII码为0

printf("%p\n", &(p[3]));

return 0;

}

#include <stdio.h>

int main()

{

char *p = "abc";    //指针变量p在栈区,字符串"abc"在常量区,即"abc"时字符串常量

*p = 'd'; //*p代表存储a的内存空间,但这个内存空间存储的值不可改变

//*p相当于p[0], 因此p[0] = 'd';同样会出现编译通过但结果出错的情况

printf("%s\n", p);

return 0;

}

//编译、连接都通过,但是运行出现"该内存不能为'written'"的应用程序错误, 因为"abc"是字符串常量;

//另外注意,如果char *p = "a"; 同样的,编译通过,但程序会出错。不能写成char *p = 'a'; 因为字符常量不能代表一个地址,编译器会提示:cannot convert from 'const char' to 'char *'

//正确的程序应该是:

#include <stdio.h>

int main()

{

char p[] = "abc";    //字符数组名p在栈区,字符串"abc"也在栈区,即"abc"不是字符串常量

printf("%s\n", p);

*p = 'd';    //*p也代表存储a的内存空间,这个内存空间存储的值可以改变

printf("%s\n", p);

return 0;

}

//注意如下相似案例:

#include <stdio.h>

int main()

{

char *p = "abc";

p = 'd';    //编译不通过, 提示cannot convert from 'const char' to 'char *'

printf("%s\n", p);

return 0;

}

#include <stdio.h>

int main()

{

char *p = "abc";

p = "def";    //编译通过,字符型常量是一个没有命名的常量, 这里将指针变量p指向了另一个字符串"def"

printf("%s\n", p);    //仔细琢磨一下, p是一个指针, 它的值就是一个字符串常量, 而字符串常量就相当于一个地址值

return 0;

}

#include <stdio.h>

int main()

{

char p[10] = "abc";

printf("%s\n", p);

*p = "def";

printf("%s\n", p);

return 0;

}

//编译不通过,提示:cannot convert from 'char [4]' to 'char'

图解:

char a[] = "hello";

char *p = "world";

● 指针与一维数组

指针与一维数组:

//定义数组元素的指针

int a[10];    //也可以对其初始化, 如int a[10]={0};

int *p;        //上两句等价于:int *p, a[10];

p=&a[0];        //上两句等价于int *p=a; 或 int *p=&a[0]; 不能是上两句等价于int *p=&a

一维数组的结构是线性的, 如图:

对于一维数组及其指针:

int a[5]={1,2,3,4,5};

int *p;

p=a;    //a是指向数组起始元素的指针常量, 是右值, 不能

一维数组首地址

(一维数组第一个元素的地址)

一维数组第n个元素的地址

开始计数)

a

a+n, 如a+4

&a

/

&a[0]

&a[n], 如&a[4]

&(*(a+0)), 即&(*(a))

&(*(a+n)), 如&(*(a+4))

p, 即&(*p)

&(*p+4), 如&(*(p+4))

● 不能用&(a+4), 否则提示error C2102: '&' requires l-value(有地址的值), 即&的运算只能是已经被声明的变量;

*(a+4)之所以正确, 是因为*的运算对象除了可以是指针变量, 还可以是地址

个元素的值

一维数组第n个元素的地址

开始计数)

a[0]

a[0+n], 如a[4]

*(&a[0])

*(&a[0+n]), 如*(&a[4])

*(a+0), 即*(a)

*(a+n), 如*(a+4)

*p

*(p+4), 如*(p+4)

#include<stdio.h>

int main()

{

int a[5]={1,2, 3, 4, 5};

int *p;

p=a;

printf("%d\n",a);

printf("%d\n",a+2);

printf("%d\n",&a);

//printf("%d\n",&(a+2));    //不合法, 因为&的运算对象只能是已经被声明的变量

printf("%d\n",p);

printf("%d\n",p+2);        //加的是两个sizeof(int), 而不是两个sizeof(p)

}

//通过指针变量获取一维数组元素的地址和值(简单案例)

#include <stdio.h>

void main()

{

int a[5]={1,2,3,4,5};

int *p;

p=a;

printf("%p\n", a);

printf("%p\n", &a[0]);

printf("%p\n", &(*(a)));

printf("%p\n", &(*p));

printf("-----------\n");

printf("%p\n", a+4);

printf("%p\n", &a[4]);

printf("%p\n", &(*(a+4)));

printf("%p\n", &(*(p+4)));

printf("-----------\n");

printf("%d\n", a[0]);    //下标法

printf("%d\n", *(&a[0]));    //地址法

printf("%d\n", *(a+0));        //地址法, *(a+0)相当于*(a)

printf("%d\n", *p);        //指针法

printf("-----------\n");

printf("%d\n", a[4]);    //下标法

printf("%d\n", *(&a[4]));    //地址法

printf("%d\n", *(a+4));        //地址法

printf("%d\n", *(p+4));        //指针法

}

//通过指针变量获取一维数组的元素(复杂案例)

#include <iostream>

using namespace std;

void main()

{

int i,a[10];

int *p;

个元素赋值

for(i=0;i<10;i++)

a[i]=i;

的元素输出到显示设备

p=&a[0];    //即p=a;

for(i=0;i<10;i++,p++)

cout << *p << endl;        //也可将后两句写成for(i=0; i<10; i++); cout<<*(a+1)<<endl;    //a+i表示数组a中的第i个元素的地址

}

● 指针与二维数组

二维数组用矩阵方式存储元素, 在内存中仍然是线性结构:

对于二维数组的指针及其元素的值:

int a[3][2]={{1,2},{11,12},{21, 22}};

int *p;

p=*a;

二维数组首地址

(二维数组第一个元素的地址)

具体含义

数据类型

二维数组第n个元素的地址

a

个一维数组指针的地址

int (*)[2]

*(a+2)+1

&a[0]

个一维数组指针的地址

int (*)[3][2]

*(pp+2)+1

&a

整个二维数组指针的地址

int (*)[2]

/

&a[0][0], 等同于:

&(a[0][0]), &(*(a[0]+0)),

&(*(*(a+0)+0))

行第0列数组元素的地址

int *

&a[2][1]

a[0]

int *

/

*a

行第0列数组元素的地址

int *

&a[0][0]+2*2+1

*a+2*2+1

p

行第0列数组元素的指针

int *

p+5

//通过指针变量获取二维数组的元素(简单案例)

#include <stdio.h>

void main()

{

int a[3][2]={{1,2},{11,12},{21, 22}};

int *p;

p=*a;    //或p=a[0]; 或p=&a[0][0];

个元素的一维数组

int (*ppp)[3][2]=&a;    //定义一个int (*)[3][2]类型的二维数组(型)指针

printf("%p\n", a);

printf("%p\n", &a[0]);

printf("%p\n", &a);

printf("%p\n", &a[0][0]); //最易理解, 常用

printf("%p\n", a[0]);

printf("%p\n", *a);

printf("%p\n", p);

printf("-----------\n");

printf("%p\n", *(a+2)+1);    //使用一维数组(型)地址的首地址

printf("%p\n", *(pp+2)+1);    //使用一维数组(型)指针指向的首地址

//不能使用二维数组(型)指针引用数组元素

printf("%p\n", &a[2][1]);    //使用第0行第0列数组元素的地址; //最易理解, 常用

printf("%p\n", &a[0][0]+2*2+1);    //使用第0行第0列数组元素的地址

printf("%p\n", *a+2*2+1);    //使用第0行第0列数组元素的地址

printf("%p\n", p+5);        //使用第0行第0列数组元素的指针

printf("-----------\n");

printf("%d\n", *(*a+0)+0);    //使用一维数组(型)地址

printf("%d\n", *(*a));        //使用第0行第0列数组元素的地址

printf("%d\n", *(*pp+0)+0);    //相当于printf("%d\n", *(*pp)

//不能使用二维数组(型)指针来取数组元素的值

printf("%d\n", a[0][0]);    //最易理解, 常用

printf("%d\n", *(a[0]));

printf("%d\n", *p);

printf("-----------\n");

printf("%d\n", *(*(a+2)+1));

printf("%d\n", *(*(pp+2)+1));

printf("%d\n", a[2][1]);    //最易理解, 常用

printf("%d\n", *(p+4));

//不能用a[5], 或*(&a[5]))取值

}

//通过指针变量获取二维数组的元素(复杂案例)

#include<iostream>

using namespace std;

void main()

{

int i,j;

int a[4][3]={{1,2,3},{4,5,6},{7,8,9},{10,11,12}};

cout << "the array is: " << endl;

for(i=0;i<4;i++)//行

{

for(j=0;j<3;j++)//列

cout <<*(*(a+i)+j) << endl;

}

}

● 熟记下表

int a[3][5]={0};

a

二维数组名称,数组首地址

int (*p)[5]=&a;

定义一个指向int [5]类型的指针变量p并初始化, p指向一个包含5个元素的一维数组

a[0], *(a + 0), *a

0行,0列元素地址

a + 1

第1行首地址

a[1], *(a + 1)

第1行,0列元素地址

a[1] + 2, *(a + 1) + 2, &a[1][2]

第1行,2列元素地址

*(a[1] + 2), *(*(a + 1) + 2), a[1][2]

第1行,2列元素的值

● 字符串与指针

//赋值字符串str1并命名为str2

#include<stdio.h>

main()

{

char str1[]="you are beautiful",str2[30],*p1,*p2;

p1=str1;

p2=str2;

while(*p1!='\0')

{

*p2=*p1;

p1++;                                /*指针移动*/

p2++;

}

*p2='\0';                                     /*在字符串的末尾加结束符*/

printf("Now the string2 is:\n");

puts(str1);                                 /*输出字符串*/

}

● 数组指针&指针数组

数组指针:a pointer to an array,即指向数组的指针

指针数组(一维的较常见):array of pointers,即用于存储指针的数组,也就是数组元素都是指针

([]的优先级大于*)

int (*p)[4];     //数组指针, p is a pointer to an integer array of size 4

int *p[4];     //指针数组, Array of 4 pointers to int

int

int (*p[8])[5]; //p is an array of pointers to integer array of size 5

#include<stdio.h>

main()

{

int a=2;

int b=3;

int *p[4];

p[0]=&a; //不能是*p[0]=&a;

p[1]=&b;

int **pp=p;    //p就是一个数组名, 代表第一个元素的地址; pp是指向指针数组p的首元素的指针(指向指针的指针)

//也可以写成int *(*pp)=p, 因为*的运算方向是从左至右, 故可以省略括号

printf("%d\n", *(p[0]));

printf("%p\n",&a);        //变量a的地址

个元素的值是变量a的地址

个元素的地址, 等同于printf("%p\n",&p[0]);

printf("%p\n",p);        //同上

printf("%p\n",pp);        //pp这个指针变量里面存储的是数组p的首地址

printf("%p\n",*pp);        //pp这个指针变量指向的另一个指针变量的值是变量a的地址

//printf("%p\n",*(pp)); //pp存储的内容是指针数组首元素的地址, 所以

}

//指针数组的指针元素指向不同的整形变量

#include<stdio.h>

int main()

{

int a=1;

int b=2;

int c=3;

int *tab[3]={&a,&b,&c};

int i;

for (i=0; i<3;i++)

{

printf("%d\n", *(tab[i]));

}

return 0;

}

//指针数组的指针元素指向不同的数组

#include<stdio.h>

int main()

{

int t1[4]={0,1,2,3};

int t2[4]={4,5,6,7};

int t3[4]={8,9,10,11};

int *tab[3]={t1,t2,t3};

int i,j;

for (i=0; i<3;i++)

{

for (j=0; j<4; j++)

{

printf("%d\n", *(tab[i]+j));

}

}

return 0;

}

//指针数组的指针元素指向不同的字符串

#include <iostream>

using namespace std;

const int MAX = 4;

int main ()

{

char *names[MAX] = {        //也可以不用宏定义MAX的值, 在方括号内什么数字也不填

"Zara Ali",

"Hina Ali",

"Nuha Ali",

"Sara Ali",

};

for (int i = 0; i < MAX; i++)

{

cout << "Value of names[" << i << "] = ";

cout << names[i] << endl;

}

return 0;

}

● 指针数组作main()函数参数

int main(int argc, char *argv[])    //等价于int main(int argc, char **argv)

//argc:参数个数(argument count); argv: 参数向量(argument vector);

这些参数不能在程序中得到, 因为main()函数是操作系统调用的, 所以实参只能由操作系统给出.

例如在DOS系统的操作命令状态下, 在命令行中包括了命令名和需要传给mian()函数的参数, 参数个数包括命令.

命令行的一般形式为:

参数2...参数n

例如:

d:\debug\1.exe hello world

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char **argv)

{

int i;

printf("共有%d个参数\n", argc);

for(i=0; i<argc; i++)

{

printf("第%d个参数是%s\n",i+1, argv[i]);

}

system("PAUSE");

return 0;

}

//得到可执行文件E:\test\Debug\test.exe

按Win键+R打开运行,在打开的运行中输入CMD打开命令行程序cmd.exe

● 指针的算术运算

对指针作算术运算有两种:

  1. 对指针加减整数, 相当于对指针指向的变量的地址作运算, 这种运算与指针指向的变量的数据类型的大小有关.
  2. 指针互减(其它运算都不行)

#include <iostream>

using namespace std;

void main()

{

int a=100;

int *p_a=&a;

printf("address:%d\n",p_a);

p_a++;

printf("address:%d\n",p_a);

char b='A';

char *p_b=&b;

printf("address:%d\n",p_b);

p_b--;

printf("address:%d\n",p_b);

int c[5]={0};

int *p_c1=&c[0];

int *p_c3=&c[3];

//两个类型相同的指针允许作减运算

printf("%d\n",p_c3-p_c1);

int *pp[5];

printf("%d\n",sizeof(pp));

}

● 指针与函数

函数存放在代码区(code area), 在编译时被分配给一个入口地址, 这个地址就是函数的指针.

可以用一个指针变量指向函数, 然后通过该指针变量调用这个函数.

① 指针形参(指针变量作函数参数)

数据类型 函数名(数据类型 *pt1, 数据类型 *pt2, ...)

② 函数型指针(指向函数的指针)

数据类型 (*函数指针名)(形参表)

※回调函数: 通过函数指针而被调用的函数

  1. 指针型函数(返回指针值的函数)

数据类型 *函数名(参数表)

{

函数体

}

● 指针变量作函数参数

//指针形参(指针变量作函数参数)

#include <stdio.h>

void func(int *a)

{

(*a)++;

}

int main()

{

int a = 1;

func(&a); //通过函数的形参间接地修改实参的值, 之所以实参为地址, 是因为子函数func的形参类型为整形指针变量

printf("%d\n", a);

return 0;

}

void func1(int *p)

{

p = NULL;

}

int main()

{

int a = 1;

int *p = &a;

func1(p);    //虽然p的值的&a的值相同, 但本质上这还是一个单向的值传递, 不是按址传递

// p指向a的地址还是NULL?p还是指向了a的地址

return 0;

}

voud func1(int **p)        //形参必须是二级指针, 因为下面的实参&p是取的指针的地址

{

*p = NULL;

}

int main()

{

int a = 1;

int *p = &a;

func1(&p);    //按址传递

// p指向a的地址还是NULL?指向了NULL

return 0;

}

● 函数型指针(指向函数的指针)

/*

int function(int x,int y); 声明一个函数

int (*f) (int x,int y); 声明一个函数指针

f=function; 将function函数的首地址赋给指针

*/

#include <iostream>

using namespace std;

int per(int a,int b);

void main()

{

int width=10,lenght=30,result;

int (*f)(int x,int y);

f=per;//定义函数指针

result=(*f)(width,lenght);    //f是指向avg函数的函数指针, 调用f就像调用avg函数

cout << result <<endl;

}

int per(int a,int b)

{

return (a+b)*2;

}

● 指针型函数(返回指针值的函数)

#include <iostream.h>

int *per(int a,int b);

int perimeter;

void main()

{

int width=10, length=30, *result;

result=per(width,length);    //per这个指针函数被调用后返回一个地址值, 将这个地址值赋给指针变量result

cout<<per(width,length)<<endl;    //per()函数被调用后, 返回指向全局变量perimeter的指针p

cout<<result<<endl;

cout<<*result<<endl;

}

int *per(int a,int b)

{

perimeter=(a+b)*2;    //如果在子函数中定义的一个变量并且改变量将用于其它函数, 那这个变量一定要是全局变量, 如果是局部变量, 局部变量的内存空间在每次函数调用时分配,在函数执行完时释放, 并得到函数的返回地址, 就算是静态局部变量, 即使在函数执行完时释放, 内存空间没有被释放, 它的作用域也只是在函数内.

int *p= &perimeter;

return p;

}

(C/C++学习笔记) 十二. 指针的更多相关文章

  1. python3.4学习笔记(十二) python正则表达式的使用,使用pyspider匹配输出带.html结尾的URL

    python3.4学习笔记(十二) python正则表达式的使用,使用pyspider匹配输出带.html结尾的URL实战例子:使用pyspider匹配输出带.html结尾的URL:@config(a ...

  2. Go语言学习笔记十二: 范围(Range)

    Go语言学习笔记十二: 范围(Range) rang这个关键字主要用来遍历数组,切片,通道或Map.在数组和切片中返回索引值,在Map中返回key. 这个特别像python的方式.不过写法上比较怪异使 ...

  3. java jvm学习笔记十二(访问控制器的栈校验机制)

    欢迎装载请说明出处:http://blog.csdn.net/yfqnihao 本节源码:http://download.csdn.net/detail/yfqnihao/4863854 这一节,我们 ...

  4. Python学习笔记(十二)—Python3中pip包管理工具的安装【转】

    本文转载自:https://blog.csdn.net/sinat_14849739/article/details/79101529 版权声明:本文为博主原创文章,未经博主允许不得转载. https ...

  5. ROS学习笔记十二:使用gazebo在ROS中仿真

    想要在ROS系统中对我们的机器人进行仿真,需要使用gazebo. gazebo是一种适用于复杂室内多机器人和室外环境的仿真环境.它能够在三维环境中对多个机器人.传感器及物体进行仿真,产生实际传感器反馈 ...

  6. JavaScript权威设计--命名空间,函数,闭包(简要学习笔记十二)

    1.作为命名空间的函数 有时候我们需要声明很多变量.这样的变量会污染全局变量并且可能与别人声明的变量产生冲突. 这时.解决办法是将代码放入一个函数中,然后调用这个函数.这样全局变量就变成了 局部变量. ...

  7. MySQL学习笔记十二:数据备份与恢复

    数据备份 1.物理备份与逻辑备份 物理备份 物理备份就是将数据库的数据文件,配置文件,日志文件等复制一份到其他路径上,这种备份速度一般较快,因为只有I/O操作.进行物理备份时,一般都需要关闭mysql ...

  8. Java基础学习笔记十二 类、抽象类、接口作为方法参数和返回值以及常用API

    不同修饰符使用细节 常用来修饰类.方法.变量的修饰符 public 权限修饰符,公共访问, 类,方法,成员变量 protected 权限修饰符,受保护访问, 方法,成员变量 默认什么也不写 也是一种权 ...

  9. Python学习笔记十二

    HTML全称:Hyper Text Markup Language超文本标记语言 不是编程语言 HTML使用标记标签来描述网页 2.  HTML标签 开始标签,结束标签.  例如:<html&g ...

随机推荐

  1. linux下获取本机的获取内网和外网地址

    1.获取内网地址(私有地址) ifconfig -a 2.获取外网地址(公网地址) curl members.3322.org/dyndns/getip

  2. ExtJS使用入门

    extjs是基于 yui 由 jack slocum开发, sencha是他们的公司, sencha是由三个项目合并起来的开源项目: ExtJS, jqTouch, Raphael(拉斐尔, 圣经中的 ...

  3. 【命令】Redis常用命令整理

    doc 环境下使用命令:       keys 命令         ?    匹配一个字符         *    匹配任意个(包括0个)字符         []    匹配括号间的任一个字符, ...

  4. (转)Nuts and Bolts of Applying Deep Learning

    Kevin Zakka's Blog About Nuts and Bolts of Applying Deep Learning Sep 26, 2016 This weekend was very ...

  5. BZOJ 1758 【WC2010】 重建计划

    题目链接:重建计划 这道题现在已经成为一道板子题了…… 这是个非常显然的0-1分数规划,可以二分答案之后树分治判定一下.注意树分治的时候如果使用单调队列,需要把所有儿子预先按最大深度排好序,否则会被扫 ...

  6. 更换主机后SSH无法登录的问题

    之前通过SSH远程一台机器(起个名字:cc),某一天把cc重装了一下系统,再SSH时显示密钥验证失败: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ...

  7. 测试报告 之 testNG + Velocity 编写自定义html测试报告

    之前用testNG自带的test-outputemailable-report.html,做出的UI自动化测试报告,页面不太好看. 在网上找到一个新的报告编写,自己尝试了一下,埋了一些坑,修改了输出时 ...

  8. 怎么运行cocos2dx 3.x simulator?

    1.simulator的好处是: 快速切换分辨率:F5快速重新启动项目: 这对于脚本语言来说都是很方便快捷的. 2.涉及到显示参数的文件有两个: ①lang,这个是菜单的语言文件,如果没有这个文件的话 ...

  9. Codeforces 768B - Code For 1(分治思想)

    768B - Code For 1 思路:类似于线段树的区间查询. 代码: #include<bits/stdc++.h> using namespace std; #define ll ...

  10. Python mysql-SQL概要

    2017-09-05 20:10:58 一.SQL语句及其种类 SQL使用关键字,表名,列名等组合成一条语句来描述操作的内容.关键字是指那些含义或者使用方法是先已经定义好的英语单词.根据RDBMS赋予 ...