文章目录
指针
一、什么是指针
指针就是存放地址的变量
内存地址:
字节:内存的容量单位,Byte,一个字节有8位,1Byte == 8bits
地址:为了能让程序能够有效的访问这些内存,计算机为内存中的每一个字节进行编号,称内存地址,简称地址
基地址:
单字节数据:对于单字节数据而言,基地址就是其所在内存的字节编号
多字节数据:对于多字节数据而言,基地址就是其所有字节在内存中地址编号最小的那个,称为基地址
取址符:&
每个变量都是一块内存,都是可以通过取址符获取其地址(基地址)
int a;
char c;
double f;
attention:
虽然不同的变量的尺寸是不同的,但是他们的地址表示却是一样的
不同的地址虽然形式上看起来是一样的,但由于他们代表的内存尺寸和类型都不同,因此它们在逻辑上是严格区分的
为什么要用指针?
指针能够指向某块内存地址,在使用过程中,它是能够实际反映数据的,特别在函数调用过程中
什么时候用指针?
如果我们将一个数据传入给函数,在函数体内数据的变化,我们想在函数体外也能表现这个变化,这个时候我们就传指针,地址传递(数据结构:链表,栈,队,二叉树)
如果将一个数据传入给函数,在函数体内只是访问这个数据,不想修改数据本身的值(函数体外部的值),这个时候我们一般就只用传递普通变量,值传递
对于一个匿名空间来讲,我们用指针来表示。(用malloc函数申请空间)
//头文件声明
#include <stdlib.h>
//函数原型,功能:申请一段匿名空间
void *malloc(size_t size);
参数列表:
size_t size:表示申请的空间的大小
返回值:void *:
成功:申请到的空间地址
失败:返回NULL
int *p = malloc(sizeof(int));
int *p1 = malloc(4);
//释放申请空间
void free(void *ptr);
free(p);
free(p1);
指针的尺寸?
指针尺寸指的是指针所在内存的字节数(取决于操作系统的位数)
32位机里面,指针变量的大小4Byte,(32bit),32位机里面,指针变量的大小8Byte,(64bit),
指针所占内存,取决于地址长度,而地址长度取决于系统寻址范围,即字长
结论:指针尺寸只跟系统的字长有关,跟具体的指针的类型无关
二、指针的基本语法
指针的定义和初始化
定义:
数据类型 *指针变量名;
int *p;
初始化:
数据类型 *指针变量名 = 地址; 或者 指针变量名 = 地址
int a;
int *p = &a;
int *p;
p = &a;
attention:
这个地址里面的数据的类型要和前面的数据类型相同
指针如何去赋值和获取数据
int型指针为例
int a = 100;
int *p = &a; //*定义指针变量
*p = 200; //*解引用,拿地址p里面的值
printf("p = %d\n", *p);
printf("a = %d\n", a);
数据获取,需要通过*解引用的方式,拿到地址对应的数据
星号 *
注释:/* …xxxx…*/
算术运算符:*(乘)
赋值运算符:*=
关于指针的应用:
指针的定义:int *p;表示声明一个指针变量
在其他地方:将*放在一个地址的前面,表示解引用(获取地址里 面的值)
指针的运算
指针的定义
指针的赋值
int a = 10;
int *p = &a;
int *p1;
p1 = &a;
解引用
指针定义过后,没有赋初值或者没有给一个合法地址给他的时候,不能直接使用解引用,相当于,使用解引用必须要保证该指针指向一个合法的地址
取地址 &变量名
一般用于指针的初始化
int *p = &a;
指针的加减法:
指针加减数字
p+3, p-1,指针加减数字表示在内存地址中,p指针指向的地址向上或者向下移动n个单位,p指向的地址没有发生改变。
具体加减多少个字节?根据指针存放的数据类型
如果是int类型指针,那么实际上,p±n,实际上4*n个字节
如果是char类型指针,那么实际上,p±n,实际上1*n个字节
如果是一整块数组,那么就是加减整块数组的n倍
指针加减指针
指针相减,两个指针必须是相同类型,表示两个地址相差多少个单位长度
int a[5] = {1,2,3,4,10};
printf("a[4] - a[1] = %ld\n", &a[4] - &a[1]);
//得到相差的3个单位长度
三、指针函数、函数指针、指针数组、数组指针、函数指针数组
指针函数:返回值是一个指针的函数
函数指针:存放一个函数的地址的指针
指针数组:每个元素都是指针的数组
数组指针:存放数组的地址的指针
函数指针数组:每个元素都是函数的地址的数组
#include <stdio.h>
void func(int a)
{
printf("%d\n",a);
return;
}
void func2(int a)
{
printf("%d\n",a);
return;
}
void func3(int a)
{
printf("%d\n",a);
return;
}
指针函数
void * fss(void * a) ///返回值为void *的指针的函数
{
return a;
}
int main(int argc, char const *argv[])
{
int a=10;
指针
int *pint=&a;
数组
int arr[5] ={1,2,3,4,5};
int arr1[2] [3] = {{1,2,3},{4,5,6}};
pint = arr;
// printf("%d\n",*(pint+2));
指针数组
// int *d,*b,*c;
// int *parr[5] = {d,b,c};
int d,b,c;
int *parr[5] = {&d,&b,&c};
数组指针
int (*p1arr) [5] = &arr;
int (*p2arr) [3] = arr1;
// printf("%d\n",*p1arr[2]);
// printf("%d\n", p2arr[0][2]);
指针函数
// void * f(int a); ///返回值为void *的指针的函数
函数指针
// void (*funp)(int a); ///存放一个函数地址的指针
void (*funp)(int a) = func;
// funp(3);
// funp(4);
函数指针数组
void( *funpa[5])()={func,func2,func3};
void(*ffff[3])(int a) = {func};
ffff[1]=func2;
ffff[2]=func3;
for (int i = 0; i < 3; ++i)
{
funpa[i](i+10);
ffff[i](i+1000);
}
return 0;
}
attention:
函数指针,函数指针数组,存放函数入口地址的时候,函数的返回值类型要一致,参数列表是可以省略不写,也就说函数的参数列表个数可以不一样
四、多级指针
二级指针:一个指向指针变量的指针
int a=10;
int *p=&a;
int **p2 = &p;
指针的拆解方法:
第一部分:指针的名称
第二部分:指针所指向的数据类型,可以是任意类型
char **p2; ///第一部分 *p2 第二部分 char *
char ***p3;///第一部分 *p3 第二部分 char **
char (*p4)[3];///第一部分 *p4 第二部分 char [3]
五、特殊的指针
void指针、空指针、零指针、const指针、char指针
1.void指针(万能指针,泛型指针,通用指针)
当不确定后面具体需要使用什么类型的时候
典型应用:malloc申请匿名空间,函数传参
void指针,必须要确定其类型才能使用
void指针最大的优点:可以赋值给任意类型指针
void *p = malloc(20);
int *p1 = p;
float *p2 = p;
char *p3 = p;
2.概念:指向一块未知区域的指针,被称为野指针,野指针是危险的。
一般我们认为没有赋初值的指针变量就是野指针
int *p; //野指针
*p = 100; //这里会发生段错误,非法使用一块未知的,未初始化的空间
printf("*p = %d\n";, *p);
危害:
引用野指针,非法访问非法的内存,常常会导致段错误(Segmentation fault (core dumped))
引用野指针,可能会破坏系统的关键数据,导致系统崩溃等严重后果
产生原因:
指针定义之后,未初始化
指针所指向的内存,被系统回收
int *p; //野指针
int *p1 = malloc(4);
free(p1);
*p1 = 100; //野指针
printf("*p1 = %d\n", *p1);
指针越界:
int a[3] = {1, 2, 3};
int *p2 = a;
printf("*(p2+3) = %d\n", *(p2+3)); //指针访问越界
如何防止:
指针定义时,及时初始化
绝不引用已被系统回收的内存
确认申请的内存的边界,谨防越界
3、空指针和零指针
很多情况下,我们不可避免会遇到野指针,比如刚定义的指针无法立即为其分配一块恰当的内存,又或者指针所指向的区域被释放了等等,一般的做法就是将这些危险的指针,指向一块确定的内存,比如零地址内存
值为NULL的指针就是空指针
int *p = NULL;
为了避免野指针和类似地址访问的异常错误,这个时候,我们就给指针赋一个NULL值
目的是:更安全,但是空指针也不能直接使用,必须给他一个有效的地址才能用,地址要有空间
空指针的NULL值,在很多系统中是0值,取决于操作系统
值为0的指针就是零指针
int *p = 0;
注意:在很多系统中,零指针和空指针是一样的,值都是为0,但是,不能保证所有系统都是这样的,那么,我们平时在使用的时候,一般是用NULL。
4.const关键字
const关键字的一般使用
我们平时将const定义的变量称为只读变量(本质上不是常量)
基本语法:
const 数据类型 变量名 = 初始值;
const int a = 10;
数据类型 const 变量名 = 初始值;
int const a = 20;
以上两种方式都可以:
注意:由于是只读变量,一旦定义完,不能修改它的值,所以,const修饰只读变量的时候,必须在定义时,给这个变量做初始化
const指针
const和指针变量一起用,可以限制指针变量本身,也可以限制指针变量指向的数据
const指针有两种形式,①常指针 ②常目标指针
常指针:const修饰指针本身,表示指针变量本身无法修改
常目标指针:const修饰指针的目标,表示无法通过该指针修改其目标
常指针在实际引用中并不常见
常目标指针在实际应用中广泛可见,用来限制指针的读写权限
const关键字在C语中有什么作用?
为了放在传入参数在函数中被修改,一般放在函数的形参上。
5.char 型指针
在C语言中,表示一个字符串
常量“hello world”
char型数组:char ch1[] = "hello world";
char型指针:char *ch2 = "hello world";
char型数组和char型指针在表示字符串的区别:
char ch1[] = "hello world"; //char型数组
char *ch2 = "hello world"; //char型指针,字符指针常量
printf("ch1 = %s\n", ch1);
printf("ch2 = %s\n", ch2);
ch1[2] = 'b';
ch2[2] = 'b'; //不可以更改
printf("ch1 = %s\n", ch1);
printf("ch2 = %s\n", ch2);
六、指针和数组
指针与一维数组:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p=NULL;
int arr[]={0,1,2,3,4,5,6,7,8,9};
p=arr;
int *p2=malloc(sizeof(int)*5);
p2[4]=5;
printf("%d\n", *(arr+1));
printf("%d\n",arr[1]);
printf("%d\n",p[9]);
printf("%d\n",*(p+9));
printf("%d\n",p2[4]);
printf("%d\n",*(p2+4));
free(p2);
return 0;
}
指针与二维数组:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p=NULL;
int (*q)[3]=NULL;
int arr[2][3]={{1,2,3},{4,5,6}};
int (*s)[3]=(int (*)[])malloc(sizeof(int)*2*3);
int *t[2]={NULL}; //指针的数组,数组存指针
int (*f)[2][3]=NULL;
int (*k)[2][3]=(int (*)[2][3])malloc(sizeof(int*)*2*3);
printf("%d\n",arr[1][1]);
printf("%d\n",*(arr[1]+1));
printf("%d\n", (*(arr+1))[1]);
printf("%d\n",*(*(arr+1)+1));
p=arr[0];
// p=&arr[0][0];
printf("%d\n",*(p+4));
q=arr;
// q=&arr[0];
printf("%d\n",q[1][1]);
printf("%d\n",*(q[1]+1) );
printf("%d\n", (*(q+1))[1]); //q+1 偏移int [3]
printf("%d\n",*(*(q+1)+1)); //*(q)+1 偏移int
s[1][1]=5;
printf("%d\n",s[1][1]);
t[0] = arr[0];
t[1] = arr[1];
printf("%d\n",*(t[0]+4)); //偏移int
printf("%d\n",*(t[1]+1));
f=&arr;
(*f)[1][1]=6;
printf("%d\n",(*f)[1][1]);
(*k)[1][1]=5;
printf("%d\n",(*k)[1][1]);
free(s);
free(k);
return 0;
}
other
#include <stdio.h>
int main(int argc, char const *argv[])
{
int a=2,*p=&a;
printf("p[0] = %d\n",p[0]);//可以看作数组第0个元素
int a[3] = {1,2,3};
// int *p1 = &a[0]; ///a == &a[0]
// // int *p1 = a;
// // int **p2 = &p1;
// // int ***p3 = &p2;
// int **p2 = (int **)&a;
// int (*a1)[3] = &a; ///第一部分 *a1 第二部分 int[3]
// int *a2[3] = {a,a+1,a+2};///第一部分(数组)a2[] 第二部分 int *
// a2[2] = &a[2];
// printf("%d\n",a[2]);
int a[2][3] = {{1,2,3},{4,5,6}};
// printf("a[1][2] = %d\n", *(*(a+1)+2));
// printf("a[1][2] = %d\n", (*(a+1))[2]);
// printf("a[1][2] = %d\n", *(a[1]+2));
// int *p1 = (int *)a;
// int *p1 = (int *)&a;
// int *p1 = (int *)&a[0];
// printf("%d\n",*(p1+5));
//int **p2 = &p1;
// printf("%d\n",*(*p2+4)); //a[1][2]
//指针数组,存指针
// int *p3[3] = {&a[0][0],&a[0][1]};
// printf("%d\n", *p3[1]);
//数组指针
int (*parr) [3] = a; //一个指针 *parr 指向int[3]
printf("a[0][0] = %d\n",**parr);
printf("a[0][1] = %d\n",*((*parr)+1));
// printf("%d\n",parr[0][0]);
// printf("%d\n",parr[1][2]);
// printf("%d\n",*(parr[0]+2) );
// printf("%d\n",*(parr[1]+2) );
// printf("%d\n",(*(parr+1))[1]);
// printf("%d\n",*(*parr+2) );
// printf("%d\n",*(*(parr+1)+1));
int (*p4)[2][3] = &a;
// printf("%d\n",(*p4)[1][1]);
return 0;
}
test:
定义一个二维数组,通过指针的方式去遍历每个元素
int a[2][3];
定义一个数组指针int (*p)[2][3]去遍历以上数组
七、指针与函数
#include <stdio.h>
int * f(int *p) //函数返回值为 int*
{
p[0]++;
return p;
}
int main(int argc, char const *argv[])
{
int i=0;
int *p=&i;
int *(*f2)(int *p); //一个指向函数的指针 返回值为指针
p=f(p);
printf("%d\n",p[0]);
f2=f;
f2(p); //p[0] 2
printf("%d\n",f2(p)[0]); //p[0] 3
return 0;
}
/*
#include <stdio.h>
print(int(*p)[5], int r, int c)//接收的是指向一维数组的数组指针
{
for (int i = 0; i < r;i++)
{
for (int j = 0; j < c; j++)
{
//printf("%d ", p[i][j]);
//printf("%d ", *(p[i]+j));
printf("%d ", *(*(p+i)+j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,5,4,6,7,9,8,4,2,5,4,5,4,5,6 };
print(arr, 3, 5);//传过去的是二维数组
return 0;
}
*/