【指针】C/C++指针相关知识
提到指针,需要联系到计算机的内存等相关的知识,这类理论知识我就不过多阐述了,可能比较枯燥,我会以实践为主来分析指针。
首先我们需要了解一下 & 符号,这个符号相信大家都不陌生,是C语言输入函数:scanf() 需要的,比如一个整形的变量a需要 scanf("%d",&a); 完成对变量a键盘输入。
但有没有发现,字符串类型也就是字符数组的输入,为什么不用加这个&符号就可以呢?
#include <iostream>
#include <cstdio>
using namespace std;
int main(int argc, char const *argv[]) {
char str[] = {'\0'};
scanf("%s", str);
printf("%s\n",str);
return 0;
}
因为这个字符数组可以看做成一个指针,具体的原因我们先放一放。
试想一下之前有没有这样的一个经历,也就是我们输入一个整形或者浮点型数据的时候,忘了加 & 符号,如下图,结果编译器没有报错,那是为什么呢?(现在比较新的GCC可能会给出警告之类的)
int main(int argc, char const *argv[]) {
int a;
scanf("%d",a);
printf("%d\n", a);
return 0;
}
最后输出的a肯定是不对的。
那我们现在分析一下a的地址的值,格式化输出%p可以打印地址。
int main(int argc, char const *argv[]) {
int a;
//scanf("%d",a);
printf("%p\n", a);
printf("%p\n", &a);
return 0;
}
结果为:
这下我们应该明白了,原来scanf从键盘输入的值没有赋给真正a的地址,而是给了0000…这个地址位置。
现在我们理解了&符号取地址的含义(当然不要和位运算里的按位与运算弄混了)
下面我们回到刚才讲的字符数组为什么输入不需要加&。
我们先打印一下字符数组str的地址和str[0]的地址,你就会发现有意思的事情了。
代码如下:
int main(int argc, char const *argv[]) {
char str[] = "Hello World!";
//scanf("%s", str);
printf("str的地址:%p\n",str);
printf("&str的地址:%p\n",&str);
printf("str[0]的地址:%p\n",&str[0]);
return 0;
}
运行结果:
注意,我们发现str、&str和str[0]的地址是一样的。
下面我分别说明一下它们三者的含义:
str : 数组第一个元素的首地址
&str : 整个数组的地址
&str[0]: 数组第一个元素的首地址
那么这样可以得出一个结论,对于字符串类型的输入,我们也可以scanf("%s",&str[0]); 这样也是正确的。(可以去试验一下)
接下来我们来详细使用一下指针,我们定义一个指针变量p,为其赋值为NULL;
这样它就是一个空指针。
代码:
int main(){
int* p = NULL;
printf("%p\n", p);
return 0;
}
运行结果
根据运行结果不难发现它是一个空指针,这样定义一个指针也是一个比较好的习惯,避免程序中出现野指针,也就是没有明确指向的地址的指针。
下面我们来了解一下指针的使用,可以看一下代码以及注释:
int main(){
int* p = NULL;
int a = 12;
//指针 p 指向变量 a 的地址。
p = &a;
//对指针变量 p 进行 *也就是取值运算。
printf("%d\n", *p);
return 0;
}
运行结果:
然后我们再做这样的一个操作,对p进行赋值,那么a的值会变吗?也就是加入这样一行代码
*p = 8; 那么a的值是多少呢?如果你已经理解了地址,那么就能猜出来a的值也是8了。
代码如下:
int main(){
int* p = NULL;
int a = 12;
//指针 p 指向变量 a 的地址。
p = &a;
//对指针变量 p 进行 *也就是取值运算。
printf("*p = %d\n", *p);
*p = 8;
printf("a = %d\n", a);
return 0;
}
运行结果:
下面来分析一下用的比较多的,也就是指针与一维数组:
一个一维数组在内存里面的地址是连续的,假设一个整形数组a = [1,2,3,4,5] ,其在内存中的地址是连续的,而一个int形的数组占4个字节,因此假设a数组首地址为0x00,那么其元素的地址应该是逐渐加4递增,如下图。
代码实现(因为%p输出的地址的数据是16进制的,比较起来不太明显,我们可以用%d输出指针的地址一样能得出结论)
int main(int argc, char const *argv[]) {
int a[5] = {1,2,3,4,5};
printf("%d\n", &a[0]);
printf("%d\n", &a[1]);
return 0;
}
运行结果:
下面是关于指针与一维数组遍历的相关代码(一些解释在注释里):
int main(int argc, char const *argv[]) {
//我们定义一个指针,和一个一维数组
int a[5] = {1,2,3,4,5};
int* p = NULL;
//指针p指向数组a
p = a;
//打印数组 a
for(int i = 0 ; i < 5 ; i++){
cout << a[i] << " ";
}
cout << endl;
//因为指针变量 p 指向 数组a的首地址,因此p也可以看做成一个数组
for(int i = 0 ; i < 5 ; i++){
cout << *(p+i) << " ";
//cout << p[i] << " ";
}
cout << endl;
//另一种利用遍历数组的方式 但是这种方式会更改地址,不建议使用
while(*p){
cout << *p << " ";
p++;
}
return 0;
}
运行结果:
现在我们理解了对于一个指针,如果指向了一个一维数组,可以把这个指针看做数组。
对于字符型的指针,需要注意的有一点,如果你让一个指针指向字符串常量,那么需要注意的是不要修改这个指针的值,建议用const char*
代码如下:
int main(int argc, char const *argv[]) {
const char* p = "HITMAN47";
printf("%s\n", p);
return 0;
}
如果要实现一个字符指针输入字符串,那么就需要开辟内存才能够输入(如果不知道malloc函数的用法,可以去查一下C++ API帮助文档)。
代码如下:
int main(int argc, char const *argv[]) {
char* p;
p = (char*)malloc(sizeof(char)*100);
scanf("%s", p);
printf("%s\n", p);
return 0;
}
对于一些指针的基本知识就分析到这里,当然还有其他很多知识,比如函数指针,结构体指针等。
由于篇幅太长,只分析到这里,以后需要的话再做深入分析,最好通过一些实际操作,对于一些基础的题目,尝试用指针来做,多进行分析,才能够理解指针,灵活使用指针。