指针到底是啥东西?很多人都有这样的问题,今天我就为大家来解答
首先看一行代码:
int a;
很显然,这行代码的用途是定义变量,那么再看一行代码
int *a;
这下懵了吧,你们以为这是一行错误的代码,但恰恰相反,这行代码能够正常运行
这行代码定义的就叫做 指针
前期我们可以把它认为是一个数据结构,像这样
int* a;
这样也是不会报错的。废话不多说,先来了解作用
1、认识指针
指针指针,顾名思义就是指向一个东西的针,但是编程中并没有针,所以我们只需要把它理解成指向一个东西的东西就行了,而它指向的那个东西叫做 地址 。
地址说白了就是存储变量的地方,比如说我们int a,就相当于定义了一个整型变量a,它有一个地址,也只有它有那个地址。
地址的格式一般是0x开头,后面一大串数字。行了,回归正题。
int* a是指向int类型的指针,同样地,char* a就是指向字符类型的指针。
int *a[10];
int[10] *a;
看上面的两行代码。
第一行是指针数组,是一个全是指针的数组,数组的每个下标都是一个指针
第二行是数组指针,是指向数组的指针。
现在,指针你基本就了解了大概,接下来直接上代码来实际操作
注意:如果你是第一次接触指针,请你务必要自己用编译器,跟着我输入我提供的代码,你也可以修改代码中的值,自己去探索指针的功能
2、学习指针
接下来我们就该学习怎么用指针来指东西了。
int a;
int *b=&a;
注意,这里的&很重要,这是取地址符,你们学习scanf的时候应该接触过。一定要加上,不然程序会报错
这段代码是让指针b指向指针a的过程。
是不是很简单?那怎么通过b来知道a的值呢
同样非常简单,只需要
cout << *b;
这里的*同样很重要!!!
很好,那指针之间怎么互相复制呢?
int* p1 = &a;
int* p2 = p1;
在看一段代码
int *a=NULL;
你一定以为NULL不能赋给指针对不对,恰恰相反,这样是完全正确的,指针不能乱赋值,万一赋值道系统重要信息的地址,你的电脑就惨咯
当然,这种错误代码我就不给了,省的你们自己非想要尝试
任何指针都可以被赋值为NULL。
接着再看一段代码
char array1[20] = "abcdefghijklmnopqrs";
char *ptr1 = array1;
int *ptr2 = (int*)ptr1;
ptr1++;
ptr2++;
cout << &array1 << endl;
cout << *ptr1 << endl;
cout << ptr2 << endl;
cout << *(char*)ptr2 << endl;
-
array1
是一个字符数组,已被初始化为包含19个字符的字符串,并以空字符\0
作为结尾,尽管在初始化时未显式包含。 -
ptr1
是一个指向字符的指针,它已被初始化为指向array1
的首地址。 -
ptr2
是一个指向整数的指针,它通过将ptr1
强制转换为整数指针类型来获得。 -
ptr1
和ptr2
都执行了递增操作。
接下来,我们逐一分析 cout
语句的输出:
cout << &array1 << endl;
这条语句输出array1
数组的地址。
cout << *ptr1 << endl;
由于ptr1
是字符指针,并且已经递增,所以这条语句输出array1
中的第二个字符,即b
。
cout << ptr2 << endl;
ptr2
是指向整数的指针,递增后它指向array1
中原本第二个字符的地址,但以整数指针的视角来看。这条语句输出ptr2
当前指向的地址。
cout << *(char*)ptr2 << endl;
这条语句首先将ptr2
强制转换回字符指针,然后解引用以输出当前指向的字符。由于ptr2
原本指向array1
的第二个字符(在转换为整数指针并递增后),并且整数指针的递增通常基于整数的大小(在大多数平台上为4字节),这条语句实际上可能输出array1
中第六个字符之后的内容,具体取决于整数大小和字节对齐。
下面是输出
上面就是指针的基础,接下来该学点新的了
函数指针
void change(int a)
{
a++; //在函数中改变的只是这个函数的局部变量a,而随着函数执行结束,a被销毁。
}
int main()
{
int age = 19;
change(age);
printf("age = %d\n",age); // age = 19
return 0;
}
实参传递给形参,是按值传递的,也就是说,函数中的形参是实参的拷贝份,形参和实参只是在值上面一样,而不是同一个内存数据对象。这就意味着:这种数据传递是单向的,即从调用者传递给被调函数,而被调函数无法修改传递的参数达到回传的效果。
有时候我们可以使用函数的返回值来回传数据,在简单的情况下是可以的,但是如果返回值有其它用途(例如返回函数的执行状态量),或者要回传的数据不止一个,返回值就解决不了了。
传递变量的指针可以轻松解决上述问题。
void change(int* pa)
{
(*pa)++; //因为传递的是age的地址,因此pa指向内存数据age。当在函数中对指针pa解地址时
//会直接去内存中找到age这个数据,然后把它增1。
}
int main(void)
{
int age = 19;
change(&age);
printf("age = %d\n",age); // age = 20
return 0;
}
那么函数的指针是什么呢
每一个函数本身也是一种程序数据,一个函数包含了多条执行语句,它被编译后,实质上是多条机器指令的合集。在程序载入到内存后,函数的机器指令存放在一个特定的逻辑区域:代码区。既然是存放在内存中,那么函数也是有自己的指针的。
其实函数名单独进行使用时就是这个函数的指针。
int add(int a,int b) //函数的定义
{
return a + b;
}
int (*function)(int,int); //函数指针的声明
function = add; //给函数指针赋值
function = &add; //跟上面是一样的
int c = function(1,2); //跟函数名一样使用
int d = (*function)(1,2); //跟上面的调用是一样的
上面的代码就实现了函数指针
除了函数,还有一个东西有指针,它常常出现在我们的程序中,但是却很容易被忽视
它就是常量,你没听错,常量也有指针
int a = 97;
int b = 98;
int* const p = &a;
*p = 98; //正确
//p = &b; 编译出错
常量指针本身就是个常量 。常量指针必须初始化,而且一旦初始化完成,则它的值就不能改变了。
同理,指向常量的指针也很容易
int a = 97;
int b = 98;
const int* p = &a;
int const *p = &a; //两者的含义是一样的
//*p = 98; 编译出错
p = &b; //正确
3、练习
最后的最后,给大家准备一些题,确保你学会了指针
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str,"hello world");
printf(str);
}
运行的结果是什么?
int array[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
int *p = array;
p += 5;
int* q = NULL;
*q = *(p+5);
printf("%d %d",*p,*q);
运行结果是什么?
int arr[5] = {0,1,2,3,4};
const int *p1 = arr;
int* const p2 = arr;
*p1++;
*p2 = 5;
printf("%d,%d",*p1,*p2);
*p1++ = 6;
*p2++ = 7;
printf("%d %d \n", *p1, *p2);
4、解析
第一道题:我们一看函数传递,指针只是浅拷贝,申请的内存在临时对象p中,并没有传递到函数外面,然后又对str地址进行写操作,str初始地址为NULL,不能进行书写,所以系统会崩溃。
第二道题:一看很开心是指针类型的加减法,下标从0开始,但是数字从1开始,所以应该是6 11。但是你忽略了q是一个NULL指针,不能进行书写,所以会崩溃。
第三道题:指针指向数组,数组退化成指针,前两个指针操作是对的。但是后面*p1++ = 6; 不可以通过p1进行值的修改,*p2++ = 7;不能对p2进行修改。所以这道题是编译出错。
好了,相信你看完本篇博客,一定对指针有所了解,点赞过20更新下一篇C++教程!