记录下自己学习指针的总结,有不足或错误之处望指出hh
1 变量的指针和指向变量的指针变量
变量的指针就是变量的地址。例如 int a = 4;则变量的指针就是a的地址。
后半句话分开解读,指针变量是个指向地址的变量,也就是指向变量地址的变量。例如 int a = 4;int *p = &a;则p就是指向变量a的指针变量。
1.1 指针变量的定义
指针是程序数据中的内存地址,而指针变量是用来保存这些地址的变量。
也就是指针就是地址,是个数字;指针变量就是存放某变量地址的变量,是个变量。
int i = 7;
int *p;
p = &i;
以上代码中,i 是一个整型变量,值为7,因为一个int型占四个内存字节,则如图:
低地址 | 0026FF30 | 0026FF31 | 0026FF32 | 0026FF33 | 高地址 |
一个小方框代表一个字节,则地址 0026FF30 ~ 0026FF33 分配给了变量 i,i 占据内存地址就是地址值最小的那个字节的地址。
指针变量只能存放地址,类型也要一致。
不可以给指针变量直接赋值,要赋值变量。
int* p1;
p1 = 3; //这是错误的
int* p1;
int a = 3;
p1 = &a; //这是正确的
1.2 指针变量的引用
1 &:取地址运算符。
2 *:指针运算符,当指针运算符不用于定义指针变量的场合时,代表是他所指向的变量。
3 (*p)++的含义
int a = 5;
int* p = &a;
(*p)++;
4 *p++的含义
int a = 5;
int* p = &a;
cout << p << endl;
*p++;
cout << p;
1.3 指针变量作为函数参数
写一个交换a,b数值的函数,以下是错误示范:
void swap(int *d1,int *d2) //并不能起到交换的作用
{
int* t;
t = d1;
d1 = d2;
d2 = t;
}
int main()
{
int a = 1, b = 2;
int* p1 = &a, * p2 = &b;
swap(p1, p2);
cout << a << " " << b;
return 0;
}
为啥不能起到交换的作用呢?
模拟一下进入swap函数的过程:
- 进去swap函数,执行到 int* t 时,d1 指向a的内存地址,d2指向b的内存地址,t随机指向一个空地址。
- 运行 t = d1 ,d1指向哪 t就指向哪,此时 t 指向 a的内存地址。
- 运行d1 = d2,d2指向哪 d1就指向哪,此时 d1指向 b的内存地址。
- 运行d2 = t,t指向哪 d2就指向哪,此时 d2指向 a的内存地址。
- 跳出函数。
虽然d1和d2地址被交换,但是和主函数里的p1,p2没有关系,并没有影响到p1和p2的值,d只是被赋予地址,函数调用结束后d和t也就变得没有意义了,并没有实质的改变p的地址,也就是说a和b并没有达到交换的目的。
正确的指针变量作为函数参数以达到交换变量的做法是下面的:
void swap(int* d1, int* d2)
{
int t;
t = *d1;
*d1 = *d2;
*d2 = t;
}
int main()
{
int a = 1, b = 2;
int* p1 = &a, * p2 = &b;
swap(p1, p2);
cout << a << " " << b;
return 0;
}
观察发现,函数体中的变换实质上是常规变量的赋值,不是指针变量之间的赋值。例如其中的d1 = d2 ,等价于a = b,这样就可以做到a和b数值交换的目的了。
函数中实参变量和形参变量数据传递是单向的值传递,指针变量作为实参形参也是这个规则,只是把值传递过去,第一个错误示例中函数swap()不可以改变实参指针变量的值,也就是说p1还是指向a,不会改变的,但是第二个中,我们改变的是实参指针变量所指向的值,也就是改变的是实参指针变量p1所指向的值:a的值,改变的是a的值,通过传递地址,用*运算符直接改变变量a,是纯粹的赋值语句。
2 数组的指针和指向数组的指针变量
数组的指针是数组的起始地址,数组元素的指针是数组中元素的地址。
2.2 数组中指针变量的定义
int a[5] = {1,2,3,4,5};
int* p;
p = &a[0];
上述代码中把a[0]元素的地址赋给指针变量 p,所以 p现在指向 a数组的首地址。因为数组名代表的就是数组的首地址(也就是第一个元素a[0]的地址),所以上下代码等价:
int a[5] = {1,2,3,4,5};
int* p;
p = a;
2.3 通过指针引用数组元素
1
*p = 9;
相当于a[0] = 9;
2
p = p + 1;
指针变量加减并不是直接数值上加1,具体加几取决于指针变量的类型,如果 p的类型是 int 就是加4,所以代码的意义是p指向数组中的下一个地址,也就是p指向 a[1]。
3
p + i;
a + i;
根据 2 可知,p + i等价于指向 a[i]。因为a指向的是数组的首地址,所以也等价于a + i。
4
*(p + i);
*(a + i);
p + i,和 a + i都是地址,所以上述为 地址所对应具体的值,及a[i]。
5
p[i]
指向数组的指针变量p,可以带个下标,跟数组元素一样,如p[i] 与 *(p + i)等价,如果p指向的是a数组的首地址,那么p[i] 和 a[i]也是等价的。
2.4 二维数组的指针和指针变量
int a[5][5];
a // 二位数组名,数组首地址,第0行首地址
a + 1 // 第一行首地址
a + 2 // 第二行首地址
a[0], &a[0][0], *a, *(a+0), &a[0] // 第0行首地址(第0行第0列地址)
a[0] + 1, &a[0][1], *a + 1 // 第0行第1列元素地址
*(a[1] + 2), a[1][2], *(*(a + 1) + 2)// 第0行第2列元素值
二维数组可以看成一维数组,一维数组中又包含一维数组。
2.5 指针数组和数组指针
指针数组就是数组,只不过数组中的元素都是指针变量,是储存指针的数组的简称。
int a[5] = { 1,2,3,4,5 };
int* p[5];
for (int i = 0; i < 5; i++)
{
p[i] = &a[i];
}
数组指针用的不是很多,是一个指针,它指向一个数组,下面是两个例子。
int a[5] = { 1,2,3,4,5 };
int (*p)[5];
p = &a; //对一维数组要用&
int* q;
q = (int *)p; //强制类型转换,不转的话报错,p 是int (*)[]类型,q 是 int * 型
for (int i = 0; i < 5; i++)
{
cout << *q<<" ";
q++;
}
int a[5][5];
for (int i = 0; i < 5; i++)
for (int j = 0; j < 5; j++)
a[i][j] = i + j;
int (*p)[5];
p = a; //二维数组不用&
int* q;
q = (int *)p;
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
cout << *q<<" "; //每一个a[i][j]依次输出
q++;
}
puts("");
p++; // 会跳到a[1] 跳20个字节
q = (int*)p;
}
数组指针相当于指向了一个一维数组,调用后赋值为数组首地址,如果为二维数组的话p++会跳到数组的第二行的首地址。
3 字符串的指针和指向字符串的指针变量
char s1[] = "test!";
char s2[] = "test!";
cout << &s1<<" "<<&s2;
此时s1和s2的地址是不一样的,虽然s的值一样,但是被保存到了两块两个不同的内存中。测试发现,两片地址只差10字节!
const char *s1 = "test!";
const char *s2 = "test!";
用字符指针实现时,发现二者的地址一样。
这是因为这段代码没有定义字符数组,c中对字符串常量在内存中会开辟出一块专门的地方来存放字符串常量,有一个固定的内存地址。所以这段字符是只读的,不可以修改。
换句话说,字符串就是元素为字符型的数组,所以指针变量的使用方法大致和数组型的指针变量类似。
4 函数指针和返回指针值的函数
4.1 用函数指针变量调用函数
如果在程序中定义了一个函数,那麽在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。和数组一样,函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫函数指针。
int (*p)(int,int);
这个语句的意思时:定义了一个指针变量p,该指针变量可以指向返类型为int型,且有两个整型参数的函数。p的类型为 int (*)(int,int)。
注意:指针函数没有++和–运算,*p的()是不可以省略的,因为优先级不一样。
int max(int a, int b)
{
if (a > b)return a;
return b;
}
int main()
{
int a = 3, b = 4;
int(*p)(int, int);
p = max;
cout << p(a, b);
return 0;
}
4.2 把指向函数的指针变量作为函数参数
int max(int a, int b)
{
if (a > b)return a;
return b;
}
int wmax(int x, int y, int (*h)(int, int))
{
int res = h(x, y);
return res;
}
int main()
{
int a = 3, b = 4;
int(*p)(int, int, int (*)(int, int));
p = wmax;
cout << p(a, b,max);
}
当然,在wmax函数中可以直接调用max函数,但是使用函数指针变量的话,可以灵活的调用相同参数不同的函数,大大提高灵活性。
4.3 返回指针值的函数
int sum;
int *max(int a, int b)
{
sum = a + b;
return ∑ // sum要设置成全局变量
}
int main()
{
int a = 3, b = 4;
int *p = max(a, b); //max函数返回的是一个地址
return 0;
}
上面的max函数是一个指针值类型的函数,返回的值是一个地址,注意的是sum一定要设置在全局变量,如果sum设置在max函数中,形成局部变量,那么在调用完max函数后地址内存会被系统回收,存在安全隐患。
5 指针的其他一些知识
1
int **p;
指向指针的指针
指针变量存放的是一个地址,事实指针变量也是一个变量,也有地址,此时指向的就是指针变量的地址。
2
指针数组作为main函数参数,数组第一个好像返回的是可执行文件的完整路径文件名?
3
指针变量可以指向为NULL,表示不指向任何有效内容。NULL表示一个宏(宏定义?),被定义为0,使p指向地址为0的 单元,同时系统也会保证地址为0的这个单元不存放有效数据。
有了这个定义后,就可以进行指针和NULL的比较了。
4
void *p;
万能型,可以指向任何数据类型,都不会报错。