指针-02
1. 指针的指针
概念:指针变量中存储的是指针的地址,又名 二维指针
语法:
数据类型 **p;
示例:
#include <stdio.h>
int main(int argc, char const *argv[])
{int num = 10;int *p1 = #int **p2 = &p1;printf("*p1是: %d\n", *p1); //*p1是: 10printf("p1的值是: %p\n", p1); //p1的值是: 0x7ffd1a6bff0printf("p1的地址是: %p\n", &p1); //p1的地址是: 0x7ffd1a6bff08printf("p2的值是: %p\n", p2); //p2的值是: 0x7ffd1a6bff08printf("p2的地址是: %p\n", &p2); //p2的地址是: 0x7ffd1a6bff10return 0;
}
2. 指针与const
2.1 指针常量 - (int * const p)
概念:
本质是一个常量,
- 该指针常量 不能修改指向的地址(就是不能指向其他变量)
- 但是可以修改指向地址中的值
注意:数组就是一个指针常量。
语法:
数据类型 * const 指针名称
例:
void fun01()
{int num = 10;// int *p = #// int num02 = 20;// p = &num02;// printf("p = %p\n", p);//指针常量//p被const修饰,int * const p = #int num03 = 30;// p = &num03; //指针指向的地址不可修改*p = 40; //但是指针指向的地址的值可以修改printf("*p = %d\n", *p); //*p = 40
}
2.2 常量指针 - (const int *p)
概念:
指向
常量(值不可修改)
的 指针,本质是一个指针
- 不能修改其指向地址的值 ,也就是 不能通过指针改变其指向的地址中的数据
- 但是可以修改其指向的地址,也就是 可以改变其存储的地址
例:
void fun02()
{int num = 10;//常量指针//指针被const修饰,// int const *p = #const int *p = #printf("p修改前的地址%p\n", p); //p修改前的地址0x7fff0b95ff58int num04 = 30;p = &num04; //指针指向的地址可以修改// *p = 40; //但是指针指向的地址的值不可以修改printf("p修改后的地址%p\n", p); //p修改后的地址0x7fff0b95ff5c
}
2.3 常量指针常量 - (const int * const p)
概念:
指向的
地址
和 只想地址中的值
都不可以修改
语法:
数据类型 const * const 指针名称
或
const 数据类型 * const 指针名称
例:
void fun03()
{int num = 10;//常量指针常量//指针和指针变量p都被const修饰,const int * const p = #int num02 = 40;// p = &num02; //指针指向的地址不可以被修改// *p = 50; //指针指向的地址的值也不可以修改
}
3. 指针与数组元素
概述:
数组:是多个相同类型的变量的集合,
每个变量
都占内存空间
,都有地址编号
。指针变量 当然 可以存放数组元素的地址。
注意:
只有
两个相同类型
的指针指向同一个数组的元素
的时候,比较大小才有意义;指向
前面元素
的指针小于
指向后面元素
的指针数组就是数组中存储的 第一个元素的地址
c语言中数组的本质是一个 指针常量
- 不可以修改指针的地址,
- 但是可以修改该指针指向地址的值
示例1:
void fun01()
{int nums[] = {1,3,5,7,9,2,4,6,8,10};int *p = &nums[3];printf("*p = %d\n", *p); //*p = 7printf("*(p+1) = %d\n", *(p+1)); //*(p+1) = 9printf("*(p+2) = %d\n", *(p+2)); //*(p+2) = 2printf("*(p-1) = %d\n", *(p-1)); //*(p-1) = 5printf("*(p-2) = %d\n", *(p-2)); //*(p-2) = 3for (int i = 0; i < 10; i++){printf("%p\n", &nums[i]);}
}/*
0x7ffc007c1610
0x7ffc007c1614
0x7ffc007c1618
0x7ffc007c161c
0x7ffc007c1620
0x7ffc007c1624
0x7ffc007c1628
0x7ffc007c162c
0x7ffc007c1630
0x7ffc007c1634
*/
示例2:
void fun02()
{int nums[] = {1,3,5,7,9,2,4,6,8,10};int *p = &nums[0];for (int i = 0; i < 10; i++){printf("nums[%d] = %d\n", i, *(p++));}
}/*
nums[0] = 1
nums[1] = 3
nums[2] = 5
nums[3] = 7
nums[4] = 9
nums[5] = 2
nums[6] = 4
nums[7] = 6
nums[8] = 8
nums[9] = 10
*/
示例3:
void fun03()
{int nums[] = {1,3,5,7,9,2,4,6,8,10};int *p1 = &nums[0];int *p2 = &nums[1];if (p1 < p2){printf("p1在p2的前面\n");}else{printf("p1在p2的后面\n");}
}
//p1在p2的前面
示例4:
void fun04()
{int nums[] = {1,3,5,7,9,2,4,6,8,10};//数组名就是数组中存储的第一个元素的地址printf("nums = %p\n",nums); //nums = 0x7ffe6b558c30printf("nums[0]= %p\n",&nums[0]); //nums[0]= 0x7ffe6b558c30
}
示例5:
void fun05()
{int nums[] = {1,3,5,7,9,2,4,6,8,10};int *p = nums;for (int i = 0; i < 10; i++){// printf("nums[%d] = %d\n", i, *(p++));// printf("nums[%d] = %d\n", i, *(p+i));// printf("nums[%d] = %d\n", i, nums[i]);// printf("nums[%d] = %d\n", i, p[i]);printf("nums[%d] = %d\n", i, *(nums+i));//c语言中数组的本质是一个 指针常量//不可以修改指针的地址,但是可以修改该指针指向地址的值// printf("nums[%d] = %d\n", i, *(nums++)); //报错}}
4. 指针与数组
4.1 数组名
数组名 其实是数组中 首元素的地址 。
例:
void fun04()
{int nums[] = {1,3,5,7,9,2,4,6,8,10};//数组名就是数组中存储的第一个元素的地址printf("nums = %p\n",nums); //nums = 0x7ffe6b558c30printf("nums[0]= %p\n",&nums[0]); //nums[0]= 0x7ffe6b558c30
}
4.2 数组指针
数组名可以赋值给一个指针变量,此时该指针指向一个数组,被称为 数组指针,其本质是一个 指针
语法:
- 指向
一维数组
语法
- 数据类型 * 指针名;
- 指向
二维数组
指针的语法
- 数组类型 (*指针名)[二维数组中一维数组的长度];
- 指向
三维数组
指针的语法
- 数组类型 (*指针名)[三维数组中二维数组的长度][二维数组中一维数组的长度];
示例:
void fun06()
{//数组指针//概念:指向数组的指针,本质是一个指针int nums[] = {1,3,5,7,9,2,4,6,8,10};int *p1 = nums;int nums02[2][4] = {1,2,3,4,5,6,7,8};int (*p2)[4] = nums02;int nums03[2][3][4] = {0};int (*p3)[3][4] = nums03;
}
4.3 指针数组
指针变量是一个变量,数组是可以存储多个类型形容的变量的勇容器,故 数组可以存储多个指针变量,此时该数组
被称为 指针数组,其本质是一个 数组。
语法:
数据类型 * 数组名[长度];
例:
void fun07()
{int a = 10,b = 20,c = 30;int *p1 = &a;int *p2 = &b;int *p3 = &c;//指针数组//存储指针的数组,本质是一个数组int *nums[3] = {p1, p2, p3};
}
4.4 字符数组与字符串指针的区别
字符数组:在内存(栈、静态全局区)
中开辟了一段空间存放字符串
,将其首元素地址赋值给 数组名,是个 指针常量
字符串指针:
-
如果指向 在
文字常量区
中存放字符串
,会将字符串
的 首地址赋值给 指针变量,此时该指针是个 常量指针 -
如果指向
栈、静态全局
区中存放的字符数组,那么则是一个 普通的指针变量,值和地址都可以修改。示例1:
void fun08()
{//str01的内存地址在栈区//str01是一个数组,所以是一个指针常量//指针常量可以修改其地址中的值//指针常量不可以修改其指向的地址char str01[] = "hello";str01[0] = 'H';printf("str01=%s\n",str01); //str01=Hello//str01 = "world"; //报错,不能指向另外的地址//str02的内存地址在文字常量区//常量指针//指针指向的地址可以修改//指针指向的地址中的元素不可修改char * str02 = "hello";printf("str02修改前的地址%p\n",str02); //str02修改前的地址0x400d28str02 = "world";printf("str02修改后的地址%p\n",str02); //str02修改后的地址0x400d4//str02[0] = 'W'; //段错误 (核心已转储)
}
示例2:
void fun09()
{//字符数组char str01[] = "hello";//指向 栈、静态全局 区中存放的字符数组char *str03 = str01;printf("str03修改内容前%s\n",str03); //tr03修改内容前hellostr03[0] = 'H';printf("str03修改内容后%s\n",str03); //str03修改内容后Hellostr03 = "world"; printf("str03修改地址后%s\n",str03); //str03修改地址后world
}
示例3:
void fun10()
{char names[][50] = {"boboy","tom","jerry","zhangsan","lisi"};//指针数组:存储的是这些值的首地址char *ns[50] = {"boboy","tom","jerry","zhangsan","lisi"};//数组指针// char (*nss)[50] = names;char (*nss)[50] = {"boboy","tom","jerry","zhangsan","lisi"};
}
5. 函数与指针
5.1 函数名就是函数地址
函数名
本身就是函数在代码区
存储该函数的地址
,故 可以赋值给一个指针变量,但是因为其在代码区的地址不可修改,所以该指针是一个指针常量
语法:
-
函数指针的
定义
与初始化
返回值类型 (*指针名称)(指向的函数的形参列表的数据类型) = 函数名;
注意:
- 指向的函数的形参列表的数据类型可以忽略不写
-
调用其指向的函数
指针名(实参列表);变量名 = 指针名(实参列表);
注意:
- 实参列表可以忽略不写
示例1:函数名
本身就是函数在代码区存储 该函数的地址
#include <stdio.h>
void test01()
{printf("test01被调用了!");
}
void fun01()
{printf("%p\n", test01); //0x400526
}int main(int argc, char const *argv[])
{fun01();return 0;
}
示例2:调用其指向的函数
#include <stdio.h>
void test01()
{printf("test01被调用了!\n");
}void fun02()
{/*函数指针的书写方式返回值类型 (*指针变量名)(形参列表)*///p1 == test01//调用函数:test01(实参列表);void (*p1)() = test01; p1(); //test01被调用了!
}int main(int argc, char const *argv[])
{fun02();return 0;
}
示例3:指向的函数的形参列表的数据类型可以忽略不写
void add(int a, int b)
{printf("sum = %d\n", a+b);
}void fun03()
{void (*p1)(int, int) = add;p1(10, 20); //sum = 30
}
示例4:
int sub(int a, int b)
{return a-b;
}void fun04()
{int (*p1)(int, int) = sub;int num = p1(10,3);printf("num = %d\n", num); //num = 7
}
5.2 指针变量作为函数的参数 (传递指针变量的地址 )
指针变量是一个变量,故可以作为 函数的形参
,此时传递的是指针变量的地址
示例:
- 指针变量作为形参在函数中定
- 指针变量作为实参,在调用函数时传递
//指针变量作为形参在函数中定义
void setNum(int *num, int i)
{num[i] = 10;
}void fun05()
{int ns[5] = {0};// int *p = ns;//指针变量作为实参,在调用函数时传递//本次传递的是指针变量的地址setNum(ns, 0);printf("ns[0] = %d\n", ns[0]); //ns[0] = 10
}
5.3 字符串指针作为实参
字符串指针作为实参,将指针指向常量区的内容传递函数。函数内部修改指针内容
时,不会影响函数外部的值
。
示例1:函数内部修改指针内容
时,不会影响函数外部的值
。
void setStr(char *str)
{str = "world";
}void fun06()
{char *s = "hello";setStr(s);printf("s=%s\n", s); //s=hello
}
示例2:
void setchar(char *str, int i)
{str[0] = 'H';
}void fun07()
{//字符数组是指针常量,可改值不可该地址char str[] = "hello";char *p = str;//setchar函数修改的是地址中的值,所以传入该指针会出现段错误// char *p = "hello"; //常量指针,可改地址不可改值setchar(p, 0);printf("str=%s\n", str); //str=Hello
}
5.4 字符串指针的指针作为函数参数
将字符指针的指针
作为函数的实参
,函数的形参使用 **q(指针的指针),函数 内部修改字符指针地址
,就可以 修改 函数外部的结果
示例:函数内部修改字符指针地址,就可以 修改 函数外部的结果
void setName(char **name)
{*name = "李四";
}
void fun09()
{char *n = "张三";char **p = &n;setName(p);printf("n=%s\n",n);
}
打印出对应的地址:
void setName(char **name)
{printf("name---->%p\n", name); //name---->0x7ffd94285208*name = "李四";printf("*name改变后---->%p\n", *name); //*name改变后---->0x400a53
}
void fun09()
{char *n = "张三"; printf("n---->%p\n", n); //n---->0x400a71printf("*n---->%p\n", &n); //*n---->0x7ffd94285208char **p = &n;printf("p---->%p\n", p); //p---->0x7ffd94285208printf("*p---->%p\n", *p); //*p---->0x400a71setName(p); printf("n修改后---->%p\n", n); //n修改后---->0x400a53printf("*p修改后---->%p\n", *p); //*p修改后---->0x400a53printf("n=%s\n",n); //n=李四
}
图示:
5.5 指针作为返回值
函数中局部指针变量做为函数的返回值,函数执行完毕后,其局部指针变量指向的地址也将被释放,外部无法使用,导致段错误
如需外部使用可以将 局部指针变量修改为 静态局部指针变量
示例:
int* my01()
{/*静态局部变量生命周期:随着所在函数第一次调用而生成,随着所在进程的执行完毕而销毁*/ static int num[5] = {1,2,3,4,5};return num;
}void fun11()
{int *ns = my01();printf("%d\n",ns[0]); //1
}