文章目录
前言
指针是C语言中最具特色的内容,也是C语言的重要概念和精华所在。
一、指针概述
1、地址和指针
指针与地址虽然有着密切的关系,但它们的概念上是有区别的,指针所标明的地址总是为保存特定的数据类型的数据而准备的,因此指针不但标明了数据的存储位置,而且标明了数据的类型,可以说指针是存储特定的数据类型的地址。指针是一种指向其他数据对象的数据类型。
每个内存单元也都有⼀个编号(这个编号就相当于宿舍房间的⻔牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。
活中我们把⻔牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C语⾔中给地址起了新的名字叫:指针。
2、指针变量的定义
int a=10;
int *p =&a;
int *p = &a:把a的地址给指针变量p
int是指针变量p指向的对象是int类型的,
*(星号)是说明p是指针变量。
5、指针变量与地址
通过这个代码可以看出整型变量a的地址和指针变量p存的地址是一样的。
这里a是整型占4个字节,有4个地址,取地址只是取出较小的地址,也可以理解为取出首地址。
4、解引用操作符
这里的* p等价于a,所以更改 * p的值也就把a的值也更改了。
5、指针变量的大小
指针变量的⼤小取决于地址的⼤小
32位平台下地址是32个bit位(即4个字节)
64位平台下地址是64个bit位(即8个字节)
6、指针变量的意义
从这个代码就可以看出char*和int *访问的字节数不一样
char *访问一个字节
int *访问四个字节
pc是存放是变量a的地址
*pc是变量a
7、const修饰指针变量
这里const放在的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。
但是指针变量本⾝的内容可变。
而const放在的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指
向的内容,可以通过指针改变。
8、指针的基本运算
指针的基本运算有三种,分别是:
• 指针± 整数
• 指针-指针
• 指针的关系运算
指针± 整数
int main()
{
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = &a[0];
int i = 0;
int sz = sizeof(a) / sizeof(a[0]);
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));//p+i 这⾥就是指针+整数
}
return 0;
}
指针-指针
int my_strlen(char* s)
{
char* p = s;
while (*p != '\0')
p++;
return p - s;
}
int main()
{
printf("%d\n", my_strlen("abc"));
return 0;
}
指针的关系运算
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = &arr[0];
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
printf("%p %d %p\n", arr, sz,p);
while (p < arr + sz) //指针的大小比较
{
printf("%d ", *p);
p++;
}
return 0;
}
假设:int i=3;int *p =&i;
i 等价于*p
&i等价于pi=3等价于*p=3
9、野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针成因
1.指针未初始化
2.指针越界访问
3.指针指向的空间释放
二、指针的使用和传址调用
void swap(int p1, int p2)
{
int tmp = 0;
tmp = p1;
p1 = p2;
p2 = tmp;
}
int main()
{
int i = 8;
int j = 6;
printf("%d %d \n", i, j);
swap(i, j);//把变量 i j传给swap函数
printf("%d %d \n", i, j);
return 0;
}
输出结果:
传值调用
实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。
void swap(int* p1, int* p2)
{
int tmp = 0;
tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
int main()
{
int i = 8;
int j = 6;
int* p1 = &i;
int* p2 = &j;
printf("%d %d \n", i, j);
swap(p1, p2);//把指针变量p1 p2传给swap函数
printf("%d %d \n", i, j);
return 0;
}
输出结果:
传址调用
在这里传的指针变量是地址所以可以交换两个变量的值,叫传址调用
三、指针、数组和地址之间的关系
1、数组名的理解
数组名就是数组首元素地址,但是有两个例外
1.sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,
单位是字节
2.&数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素
的地址是有区别的)
这里可以看出&arr+1就跳过了整个数组的大小。
2、 一维数组传参的本质
从这组代码可以看出,数组的大小在main函数和test函数中不一样,这是因为数组传参的本质是把数组的首元素传了过去,arr的首元素地址的大小就是4,所以在test函数中数组的大小就是1。
3、指针与一维数组的对照关系
4 、指针数组
指针数组:存放指针的数组
指针数组的每个元素都是⽤来存放地址(指针)的。
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
//数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中
int* parr[3] = { arr1, arr2, arr3 };
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("%d ", parr[i][j]);
}
printf("\n");
}
5 、数组指针
数组指针:指向数组的指针(数组指针变量存放的是数组的地址)
数组指针变量是指针变量,一般用于二维数组
void Print(int(*p)[4], int r, int c)
{
int i = 0;
for (int i = 0; i < r; i++)
{
int j = 0;
for (int j = 0; j < c; j++)
{
printf("%d ",*(*(p+i)+j));
}
}
}
int main() {
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
Print(a, 3, 4);
return 0;
}
void Print(int(*p)[4], int r, int c)
{
int i = 0;
for (int i = 0; i < r; i++)
{
int j = 0;
for (int j = 0; j < c; j++)
{
printf("%d ",p[i][j]);
}
}
}
int main() {
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
Print(a, 3, 4);
return 0;
}
通以上两个代码可以看出p[i][j]等于*(*(p+i)+j)
⼆维数组传参本质上也是传递了地址,传递的是第⼀行这个⼀维数组的地址,
6、指针数组和数组指针的区别
指针数组和数组指针在定义时非常相似,只是括号的位置不同:
int *(p1[5]); //指针数组,可以去掉括号直接写作 int *p1[5];
int (*p2)[5];//数组指针,不能去掉括号
7、指针与二维数组
int a [3][4]:
a=二维数组的首地址;
a+i=第i行的首地址;
a[i]=*(a+i) —第i行第0列的元素地址;
a[i]+j <----> *(a+i)+j —第i行第j列的元素地址;
*(a[i]+j) 和 *(*a+i)+j <—>a[i][j]。
int main()
{
int a[3][2] = { {99,88},{77,66},{88,99} };
printf("%p %p\n", a[1] , *(a + 1) );
printf("%p %p\n", a[1] + 1, *(a + 1) + 1);
printf("%p %p %p\n", *(a[2] + 2), *(*(a + 2) + 2),a[2][2]);
return 0;
}
输出结果:
a+i=&a[i]=a[i]=*(a+i)=&a[i][0],值相等,含义不同:
a+i<—>&a[i] ,表示第i行首地址,指向行;a[i]<—>*(a+i)<—>&a[i][0],表示第i行第0列的元素地址指向列。
int main()
{
int a[3][2] = { {99,88},{77,66},{88,99} };
printf("%p %p %p %p %p\n", a+1, &a[1], a[1], *(a + 1), &a[1][0]);
printf("%p %p %p %p %p\n", (a+1)+1, (&a[1])+1, (a[1]) + 1, (*(a + 1)) + 1, (&a[1][0]) + 1);
return 0;
}
输出结果:
四、字符指针变量
这⾥是把⼀个字符串的首地址放到ps指针变量
int main()
{
const char* ps = "hello";//这⾥是把⼀个字符串的首地址放到ps指针变量
printf("%s\n", ps);
return 0;
}
这里的字符串“hello”可以理解为一个字符数组,但是它的值不可以修改。
字符串常量出现在表达式中时它的值是首元素地址。
五、二级指针(指向指针的指针)
int main()
{
int a = 10;
int* ps = &a;
int** pps = &ps;
printf("%d %p %p\n",a, &a,a);
printf("%d %p %p\n",*ps, ps,* ps);
printf("%d %p %p\n",**pps, *pps,**pps);
return 0;
}
六、函数指针
函数指针:用来存放函数的地址