指针是用来保存内存地址的变量。
定义
type * pvalue;
一、“&”是用来取地址的。
因为pvalue是个指针,他存的是地址,可以把地址赋值给指针,比如,scanf()中的“&”符号,就是用来取地址的,如:
int a=8;
int * pvalue=&a;
习惯上说,pvalue指向了a。
二、“*”是用来解引用的。
也就是获得指针所指向的地址处的数据
对上面的例子
printf("%p",&a);//打印a的地址
printf("%p",pvalue);//打印a的地址
printf("%p",*pvalue);//打印8(pvalue所指向的地址的值)
也可以通过“*”符号对地址所在的值进行赋值
如:
*pvalue=0x1122;//修改pIntValue所指向地址处的内容
三、指针可以用来交换两个数的数据
我们知道,通过形参来进行数据的交换,对实参是不会有影响的。原因是创建一个函数,在内存的栈中,会重新分配空间来保存变量,也就是说,实参和形参在内存中的位置是不同的,他们的内存地址是不一样的。
可以使用指针来交换数据
#include<stdio.h>
void exchange(int * v1,int * v2){
int temp=*v1;
*v1=*v2;
*v2=temp;
}
int main(int argc, char* argv[]){
int value1=8;
int value2=5;
exchange(&value1,&value2);
printf("%d,%d",value1,value2);
//纯手打,自己验证
return 0;
}
四、指针的强制类型转换
C语言中,不同类型的指针之间是不能直接赋值的,否则会报错滴。
(转换的类型)变量;
如:
int * ivalue=NULL;
short a=1;
ivalue=(int *)&a;
五、指针的具体内涵
- 指向何处
- 解释方式(数据长度、编码方式等)
也就是说多个指针可以同时指向一个地址,因为他们的解释方式不同,所以解引用得到的值也会不同。
void*类型的指针
我们还可以定义void*类型的指针,void指针其实就是没有解释方式
其他有解释方式的指针可以直接赋值给void指针,
反过来就不行了。
六、指针的加减
指针的加减和普通的加减有点区别
公式:
指针+数字 =指针地址+ 数字*sizeof(type)
也是好理解的,可以这样想:因为指针是有类型的,他是指向一个同类型的变量的地址,因此,如果他加减,实际上就是指针的移动,而移动一个所变化的值正好是一个这种类型的大小
七、数组作为参数传递时的退化
数组作为参数传递时,其实传递的是数组的首地址,实参数组变为形参时,会退化为指针,也就是说可能会丢失信息。
八、变参函数
变参函数可以通过指针来实现
它的核心技术是:
通过第一个参数的地址,定位到其他所有参数
也就是说我们如果找到了他的第一个参数的地址,其他地址也就找到了
看个例子:
目的:创建一个实现可以任意个数相累加的变参函数
通过调试,可以发现变参函数的数据存储是有规律的,只要找到他第一个参数的地址,那其他的数组也就找到了,因此我们可以用下面的方法来实现我们的需求
#include<stdio.h>
//count为要输入的数据个数
int add(int count, ...){
int * pvalue = &count;
int sum = 0;
for (int i = 0; i<count; i++){
sum += *(pvalue + 1 + i);
}
return sum;
}
int main(int argc, char*argv[]){
int sum1=add(4, 1, 2, 3, 4);//
int sum2=add(5, 1, 2, 3, 4, 8);//
printf("%d,%d",sum1,sum2);
return 0;
}
九、字符串的两种定义方式的区别
C语言中,对于字符串,实际是一种约定。
C语言中字符串是约定以0x00为结尾的一系列ASCII码码值,在实际的调用中,往往只传递字符串的首地址。
- char sayHello[] =“hello world”;
- char * sayHello=“hello world”;
第一种:
- 用char类型的数组来存储hello world,数组是存放在栈空间的
第二种:
- 用指针的方式来声明字符串,他内部的存储方式 ,“hello
world”是存放在全局区的而指针的地址还是在栈区,也就是说先将全局区的空间开辟出来并把“hello
world”赋值到这块空间,然后在返回其首地址给指针。
全局区用来存放字符串的内存属性,默认是只能读,不能写的。
因此以上的两种方式的不同:
指针方式的不可以对其中的值进行重新赋值。
十、与字符串有关的库函数
- strlen:返回字符串的长度
- strcat:字符串的拼接
- strcpy:字符串的复制
十一、指针数组与数组指针
指针数组:其本质是个数组,只不过数组中存放的是指针,举个例子
int * parr[3];
#include<stdio.h>
main(int argc, char*argv[]){
int * parr[3];
int a,b,c;
parr[0]=&a;
parr[1]=&b;
parr[2]=&c;
return 0;
}
数组指针:本质是个指针,指向一个数组,这个就有点难受了。
int (*pvalue)[3] ,因为“[]”的优先级要高于解引用的符号,因此不加()代表的是数组,加()代表先做指针运算,也就是定义一个int [3]类型的数组指针。
如下:
#include<stdio.h>
int main(int argc, char* argv[])
{
int iValueAry[3];
int(*pValueAry)[3] = &iValueAry;//定义一个int[3]类型的指针,指向iValueAry[3]
printf("%p\r\n", pValueAry);//数组返回的是其首地址
printf("%p\r\n", iValueAry);//数组返回的是其首地址,与上面的相等
printf("%p\r\n", iValueAry + 1);//首地址的址+4,因为一个int是4个字节
printf("%p\r\n", pValueAry + 1);//首地址的址+3*4,因为一个int[3]类型的数组有3个int类型的数,所以是3*4
return 0;
}
十二、数组指针与二维数组
先上代码
#include<stdio.h>
int main(int argc, char* argv[])
{
int iValueAry[2][3]={1,2,3,4,5,6};
int(*pValue)[3] = iValueAry;
printf("%p\r\n", pValue);
printf("%p\r\n", pValue + 1);
return 0;
}
所谓二维数组,其实他的名字就是一个数组指针,可直接将其赋值给上面的pValue,因为是个int[3]数组类型的指针,所以这个指针是指向一个数组(iValueAry),因此若是pValue+1,那就是移动到二维数组的第二行了,所以数组指针也叫做行指针。
#include<stdio.h>
int main(int argc, char* argv[])
{
int iValueAry[2][3]={1,2,3,4,5,6};
int(*pValue)[3] = iValueAry;
printf("%p\r\n", pValue);//打印iValueAry的首地址,因为我们知道,数组名就是数组首地址
printf("%p\r\n", pValue + 1);//打印首地址+1*3*4,因为pValue的类型是int[3],所以一下是加了3个int类型的字节数,而一个int是4字节,所以是1*3*4
printf("%p\r\n", pValue + 2);//打印首地址+2*3*4,道理同上
printf("%p\r\n", *pValue);//因为pValue是一个指针,指向iValueAry,指针解引用就是获取指针所指向地址的数据值,因此对pValue解引用理论上会得到一个int [3]类型的数组,也就是{1,2,3},但是数组是通过首地址来传送的,因此,打印的结果也是其首地址
printf("%p\r\n", **pValue);//对pValue的第一次解引用得到一个int[3]类型的数组,不过是以首地址的方式返回的,想一下,如果在对其int[3]类型解引用,就会得到数组中的真实数据了
return 0;
}
==printf("%p\r\n", **pValue); ==只有这个不好理解。
其实可以这样想,pValue是指向一个二维数组的有三个元素的数组指针,第一次对其解引用,得到的是二维数组中的一个3个元素的数组,在进行解引用才是对其元素的真实数据的显示。
十三、用指针来返回多个值
一般来说,函数的返回值只有一个,但运用指针,我们可以在一个函数中带出不止一个参数。
比如,可以指针做为函数的形参,
#include<stdio.h>
void back(int * a,int * b){
//带出两个值
*a = 6;
*b = 1;
}
int main(int argc, char* argv[])
{
int a;
int b;
back(&a,&b);
printf("%d,%d",a,b);
return 0;
}
用来带出数据的参数,称为传出参数
十四、通过传出参数带出字符串(二级指针)
错误的实例:
#include<stdio.h>
void FunOutString(char* pOutValue)
{
char* pszContent = "Hello, world\r\n";
pOutValue = pszContent;
}
int main(int argc, char* argv[])
{
char* pszValue = NULL;
FunOutString(pszValue);
printf("带出后的结果:%s\r\n", pszValue);
return 0;
}
输出结果还是NULL。
指针本身是个变量,是存在于栈区的,当程序运行到FunOutString函数时,会在内存中重新开辟pOutValue 参数的空间,这和我们想改变那个值不是同一个,因此是没法改变的。
正确的实例:
#include<stdio.h>
void FunOutString(char** pOutValue)
{
char* pszContent = "Hello, world\r\n";
*pOutValue = pszContent;
}
int main(int argc, char* argv[])
{
char* pszValue = NULL;
FunOutString(&pszValue);
printf("带出后的结果:%s\r\n", pszValue);
return 0;
}
pszValue是一个指针,那&pszValue就是取这个指针的地址,其实对于这个东西整体,就是一个二级指针的类型,因为指针本来就是地址,那对地址在取地址当然就是个二级指针了,用二级指针就可以改变pszValue 的地址对应的值了,这个和上面说的通过指针交换两个数的值(第三大模块)有点累似,不好理解可以回去看看。因为要传入一个指针的地址(也就是二级指针),所以形参就是char 双星类型,然后对传入的这个char双星类型的进行一次解引用,就会得到其地址处的值,然后进行赋值就可以了
所谓的二级指针,他的本质是一个指针,只不过,对于其解引用后,得到的还是一个指针。
十五、函数指针
所谓的函数,其实是内存中的一段机器码。 既然函数是内存中的一段机器码,那么函数也就有所谓的首地址的概念。
可以自己试试,函数名打印的结果,是一个(全局区)的地址。
函数名是首地址,那么就可以使用指针变量存放它。
来存放函数地址的指针,就称为函数指针。我们使用函数指针,其实就是用来调用对应的函数。
如:
#include<stdio.h>
void function()
{
printf("hello");
}
int function2(int x, int y)
{
return x + y;
}
int main(int argc, char* argv[])
{
void(*p)();//定义一个函数指针
p = function;//将指针指向function函数
(*p)();//对p进行解引用来调用函数
int(*p2)(int x, int y) = NULL;//指向int返回值,int,int参数的函数
p2= function2;
return 0;
}
定义:
函数类型+(*+指针名)+(形参参数类型)