一.指针的概念
指针(Pointer)是一种在编程语言中常用的概念,简单概括它,其实就是Java中地址值的概念。
二.指针变量
1.概念
- 指针变量是一种特殊的变量,用于存储内存地址
- 指针变量的值是一个内存地址,该地址指向存储在计算机内存中的数据
2.定义
格式:数据类型 *指针变量名
示例:
int *ptr; // 定义一个整数指标变数ptr
float *ptr_float; // 定义一个浮点数指标变数ptr_float
char *ptr_char; // 定义一个字符指标变数ptr_char
三.指针的分类
-
字符指针
char *p
-
短整型指针
short int *p
-
整形指针
int *p
-
长整型指针
long int *p
-
float型指针
float *p
-
double型指针
double *p
四.指针和变量
1.指针的概念
- 指标是一种特殊的变数,用于存储内存地址
- 指标变数的值是一个内存地址,它指向存储在该地址的数据
2.变量的概念
- 变数是在程式中用于存储和操作数据的名称化位置
- 变数具有数据类型,可以存储不同类型的数据,如整数、浮点数、字符等
3.使用指针操作变量
(1)取址运算符(&)
int num = 10;
int *ptr = # // 将指标ptr指向变数num的地址
(2)取值运算符(*)
int num = 10;
int *ptr = #
printf("%d", *ptr); // 输出ptr所指向的变数num的值,即10
4. 指标和变数的关系
- 指针可以用于间接访问变量的内容,从而实现对变量的操作。
- 指针还可以用于动态内存分配,使程序能够灵活地管理内存空间。
五.指针和数组元素
(1)指针数组取值
**概述:**可以通过指针变量加取值的方法应用数组元素
示例:
p 是第 0 个元素的地址,p+2 是 a[2]这个元素的地址。对第二个元素的地址取值,即 a[2]
int a[5];
int *p;
p=a;
*(p+2)=100;//相当于a[2]=100
(2)指针运算
一般只有在一片连续的内存空间中才有意义,而这种空间一般是数组,所以放在这里。
(1)指针与整数的加减运算
① 概述
数据类型占多少字节,每单位就移动多少字节
指针与整数的加减运算,表示是指针所指向的内存地址的移动整数就是移动的单位(字节)而这个移动的单位与其指向的数据类型有关
② 注意点
- 只有指向连续的同类型数据区域,指针加、减整数才有实际意义
例如数组:对指针的加减就相当于改变索引,使其指向下一个数据
③ 格式:
格式:指针±整数
范例:
int类型占用内存为4字节,*h+1
相当于向前移动了4个字节
int p = 1;
int *h = &p;
*h+1;//相当于向前移动了四个字节
④ 例子
尝试使用指针整数加减的方式改变索引,间接实现遍历数组的目的
#include
#define LENGTH 5
int main() {
int arr[LENGTH] = {10,20,30,40,50};
//方式1:传统直接访问的方式
for(int i = 0;i < LENGTH;i++){
printf("%d ",arr[i]);
}
printf("\n");
//方式2:使用指针访问
int *p = &arr[0];
for(int i = 0;i < LENGTH;i++){
printf("%d ",*(p+i));
}
return 0;
}
(2)指针比较大小、
① 概述
通常用于确定数据在数组中的位置
**前提:**只有两个相同类型的指针指向同一个数组的元素的时候,比较大小才有意义。
**大小规则:**指针的大小随索引的增大而增大,即越往后的索引越大
② 示例
结果: p < q,即在数组中q元素的索引在p索引后面
#include
int main(int argc, char *argv[])
{
int a[10];
int *p, *q, n; // 如果在一行上定义多个指针变量的,每个变量名前面加*
//上边一行定义了两个指针 p 和 q ,定义了一个整型的变量 n
p=&a[1];
q = &a[6];
if (p < q)
{
printf("p q)
{
printf("p>q\n");
}
else
{
printf("p == q\n");
}
return 0;
}
六.指针数组
1.概述
**指针数组:**指的是存放相同类型指针变量的数组
作用:
- 存储和操作多个指针。
- 在函数中传递和操作多个指针。
- 处理需要动态管理的数据集合
2.定义
格式:类型说明符 * 数组名[元素个数];
定义示例:
int *ptr_arr[5]; // 整數指標數組,包含5個元素
char *ptr_arr[10]; // 字符指標數組,包含10個元素
float *ptr_arr[3]; // 浮點數指標數組,包含3個元素
3.示例
int num1 = 10, num2 = 20, num3 = 30;
int *ptr_arr[3] = {&num1, &num2, &num3};
for (int i = 0; i < 3; i++) {
printf("%d ", *ptr_arr[i]); // 輸出指向的值
}
七.多级指针
二级指针存储目标指针,必须对目标指针进行取址操作
例如:
**q
要存储*p
的方式是q = &p
1.概述
**多级指针:**是指指向指针的指针,每一个指针都包含一个内存地址,该地址指向另一个指针
作用:
- 在函数传递和操作多层数据结构,以便对其进行访问和修改
2.定义
(1)简述
定义几级指针,就在指针变量后加几个
*
号,取值时也与之对应即可取到最终指向的数据
int num = 10;
int *ptr = #
int **ptr_ptr = &ptr; // 定義了一個指向指標的指標
八.字符串和指针
1.字符串的概念
(1)字符串的含义
字符串就是以\0
结尾的若干的字符的集合:比如“helloworld”,是由多个字符组成的,其结尾也有个隐藏的\0
(2)字符串的地址
字符串的地址,是第一个字符的地址。
如:字符串“helloworld”的地址,其实是字符串中字符h
的地址。
2.字符串的存储形式
(1)数组
**概述:**字符串存储在数组中,就是在内存(栈、静态全局区)中开辟了一段空间存放字符串
**简述:**用数组定义并存储字符串,可修改
示例:char string[100] = "I love C!"
,数组内每一个索引都对应字符串中的字符,有字符构成数组
(2)文字常量
**概述:**在文字常量区开辟了一段空间存放字符串,将字符串的首地址付给指针变量。
**简述:**用指针定义字符串,不可修改,但可修改其指向
示例:char * str = "I love C!"
,指针变量str只记录了第一个字符的地址,字符串本身其实是存储在字符常量区的
(3)堆区
**简述:**使用malloc等函数,动态申请内存,将字符串拷贝到堆区
示例:
char *str =(char*)malloc(10);//动态申请了 10 个字节的存储空间,首地址给 str 赋值。
strcpy(str,"I love C");//将字符串“Ilove C!”拷贝到 str 指向的内存里
3.修改字符串
(1)概述
字符串是否可以修改,与其存储的方式有关
- 数组存储:可修改
- 文字常量区存储:不可修改
- 堆区存储:可修改
(2)字符串的修改
① 数组
Ⅰ.概述
**前提:**数组没有被定义成不可修改,即用const修饰
存储在数组中的字符串是可以修改的
Ⅱ.修改示例
通过索引修改存储字符串的字符数组中字符的值,以达到修改字符串的目的
代码:
在C语言中,如果不指定数组的长度,那么该数组长度会与初始化字符串一致。
这里涉及修改,且要修改的长度大于现有数组,所以必须指定数组长度,否则会报错
#include
int main() {
char str[20] = "I love you!";//这里必须指定数组长度,因为下面涉及修改的句子比现有句子长
printf("old str=%s\n", str);
str[0] = 'y';//通过修改对应索引的值实现修改字符串的目的
printf("new str=%s\n", str);
strcpy(str, "You love me!");//使用strcpy直接修改整个句子
printf("new str=%s\n", str);
}
输出:
old str=I love you!
new str=y love you!
② 文字常量区
Ⅰ.概述
存放在文字常量区的内容是不可以修改的,但是可以通过修改指针的指向,让其指向其它地方,进而间接的实现"修改"数组的操作
Ⅱ.示例
可以通过将指针str
指向另一个字符串,间接实现"修改"字符串,实际他修改的是指针所指向的值
代码:
#include
int main() {
char* str = "I love you!";
printf("str的值%s\n",str);
str = "you love me!";
printf("new的值是%s\n", str);
return 0;
}
输出:
str的值I love you!
new的值是you love me!
③ 堆区
Ⅰ.概述
存储在堆区的字符串是可以修改的
Ⅱ.示例
这里第一次使用strcpy设置字符串,紧接在再次使用strcpy拷贝新的字符串进入堆,实现了修改的操作
#include
#include
int main() {
char * str = (char*)malloc(10);//使用malloc函数,去堆申请10个字节的空间
strcpy(str,"I love you!");//使用strcpy函数,将字符串复制到堆空间中
printf("old str 的值:%s\n", str);//str其实是一个指针,其指向了堆空间中的字符串
strcpy(str, "you love me!");//再次使用strcpy对其值进行修改
printf("new str的值:%s\n", str);
return 0;
}
输出:
old str 的值:I love you!
new str的值:you love me!
4.总结
1、指针可以指向文字常量区
-
指针指向的文字常量区的内容不可以修改
-
指针的指向可以改变,即可以给指针变量重新赋值,指针变量指向别的地方。
2、指针可以指向堆区
-
指针指向的堆区的内容可以修改。
-
指针的指向可以改变,即可以给指针变量重新赋值,指针变量指向别的地方。
3、指针也可以指向数组(非 const 修饰)
例:char buf[20]="hello world"; char *str=buf;
这种情况下
-
可以修改 buf 数组的内容。
-
可以通过 str 修改 str 指向的内存的内容,即数组 buf 的内容
-
不能给 buf 赋值 buf=“hello kitty”;错误的。
-
可以给 str 赋值,及 str 指向别处。 str=“hello kitty”
九.数组(的)指针
区别于指针数组,二者不是一个东西
1.概念
简单讲,就是指向数组的指针啦
数组指针是一个指向包含固定数量元素的数组的指针。通过使用数组指针,可以更方便地操作数组元素,包括遍历数组、访问特定元素等。
2.格式
格式:指向数组的数据类型 (*指针变量名) [指向的数组的元素个数]
例如:int(*p)[5]
,指向Int型数组,指向数组的元素个数是5个
3.应用
- 函数参数的传递
- 函数接收数据用的形参
-
多维数组的处理
- 数组指针本身就能代表其指向的数组,对其操作直接作用于其指向的数组,可以很方便的处理多维数组
- 动态内存分配
- ...(学海无涯)
(1)函数参数的传递
① 概述
当需要将一个数组作为参数传递给函数时,可以将数组的指针传递给函数。这样,函数可以通过指针操作数组,而不需要复制整个数组。
② 原理
数组名≠数组指针
数组本质上是一个具有固定大小和类型的内存块,而数组指针则是指向这个内存块的指针。通过将数组名赋值给一个指向正确类型的指针,可以获得指向数组的指针。
③ 代码
**数组指针的定义:**确保数组指针的参数与要传递的二维数组列一致即可
#include
void fun();
int main() {
int arr[3][5] = {
{3,6,7,2,3},
{7,0,2,1,6},
{2,5,4,8,6,}
};
fun(arr, 10);
return 0;
}
void fun(int(*p)[5],int a) {
int b = p[0][1] + a;
printf("计算结果:%d", b);//计算结果:16
}
(2)多维数组的处理
① 概述
在处理多维数组时,这种指针类型特别有用。
- 它可以指向一个二维数组的行,使得可以更方便地遍历和操作数组的每一行。
② 功能
下面例程实现了下面二者
-
一维数组指针配合二维数组使用
一位数组指针每+1,就会下移一个一维数组(二维数组列与一维数组参数一致)
-
通过指针数组进行多维数组的遍历
③ 代码
- 先用数组指针去表示array数组
- 之后再进行遍历
-
(*p)[j]
表示第一行的数组的第j个元素 - 遍历完毕后,通过
p++
将其指向下一个数组- 走完这后,p会++,j的值会清零,重新从0开始
-
#include
int main() {
int array[3][5] = {
{1, 2, 3, 4, 5},
{6, 7, 8, 9, 10},
{11, 12, 13, 14, 15}
};
int(*p)[5] = array;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
printf("%d ", (*p)[j]);
}
p++;//一维数组指针+1,指向二维数组中的第二个一位数组,进而完成遍历的目的
printf("\n");
}
/*
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
*/
return 0;
}
4.数组名字变指针
(1)概述
对数组名字取地址就可以将其变成一个数组指针。
(2)结论
a
和&a
虽然指向一个地址,但a
和&a
的指针类型是不同的
a
是int*
类型的指针&a
是数组指针
- 对几维数组取地址,就可以得到几维数组指针
- 对其加一操作,是几维数组指针就跳一个几维数组的元素
(3)示例
&a
使一维数组变成了一个一维数组指针,是 int(*p)[10]类型的。
(&a) +1
和&a
相差一个数组即 10个元素即 40个字节。
#include
int main() {
int a[10]; printf("a=%p\n", a);//000000A6340FFC68
printf("a+1=%p\n", a + 1);//a+1=000000A6340FFC6C
printf("&a=%p\n", &a);//&a=000000A6340FFC68
printf("&a +1=%p\n", &a + 1);//&a +1=000000A6340FFC90
//000000A6340FFC68 - 000000A6340FFC90 = 40(十进制)
return 0;
}
(4)数组名字和数组指针的区别
① 相同点
a 是数组的名字,是 a[0]的地址,p=a 即 p 保存了 a[0]的地址,即 a 和 p 都指向 a[0],所以在引用数组元素的时候,a 和 p 等价
**例如:**a[2]、(a+2)、p[2]、(p+2) 都是对数组 a 中 a[2]元素的引用。
② 不同点
-
a是常量(既是数组地址又是第一个元素地址),p是变量
可以用
=
号给p赋值,不能用=
号给a赋值 -
对a取地址和对p取地址的结果不同
- a是数组名字,取地址的结果是数组指针
- p是指针变量,取地址的结果是二级直至
5.数组指针取*
(1)概述
数组指针取*
,不是取值的意思,而是指针类型的转换。
例如:
对数组指针取*
,会得到"降维"效果
-
一维数组指针取*,会变成
int*
类型的指针,指向第0个元素的地址 -
二维数组指针取*,会变成一维数组指针,指向第0个一位数组
-
三位数组指针取*,会变成二维数组指针,指向第0个二维数组
-
以此类推
(2)演示
#include
int main()
{
int a[3][5];
int(*p)[5]; p = a;
printf("a=%p\n", a);//a 是一维数组指针,指向第 0 个一维数组,即第 0 行
printf("*a=%p\n", *a);//*a 是 第 0 行第 0 个元素的地址,即 &a[0][0]
printf("*a +1=%p\n", *a + 1);//*a +1 是第 0 行第 1 个元的地址,即&a[0][1]
printf("p=%p\n", p);//p 是一维数组指针,指向第 0 个一维数组,即第 0 行
printf("*p=%p\n", *p);//*p 是第 0 行第 0 个元素的地址,即 &a[0][0]
printf("*p +1=%p\n", *p + 1);//*p +1 是第 0 行第 1 个元的地址,即&a[0][1] return 0;
}
十.指针与函数
1.指针作函数参数
函数修改值直接作用于主调函数变量所存储的数据
(1)概述
**含义:**简单来说就是将指针即地址作为参数传递给函数。
**作用:**通过函数去修改变量的值(数组用另外一套)
(2)特点
-
函数可以直接修改地址值指向的值
即改变主调函数中变量的值
(3)注意点
① 传值
必须传变量的地址值,即是指针变量
- 通过用
&
取地址,以地址(指针)作为参数传递
② 赋值
因为接收的是指针,需要通过*地址
变回其指向的值才能进行赋值操作
- 进行取值,相当于复原回了主调函数变量的值
(4)示例程序
#include
void fun(char** p);//函数的声明
int main() {
char* str = "Hello World!";//其本身也是一个指针
fun(&str);//这里要对指针进行取址
printf("修改后的值:%s\n", str);//修改后的值:Hello C !
return 0;
}
/*这种方式直接修改了str所指向数据的值*/
void fun(char** p) {//用二级指针进行接收
*p = "Hello C !";//直接修改指针指向位置的数据
}
2.函数传数组
我觉得啊!咱函数传指针,就用Java那种形式传就好了
(1)特性
Java中的数组是按数值传递,它相当于复制一份给函数,函数没办法操作数组的原始数据,只能操作复制过去的数据
C语言中的函数传数组是按引用传递的,即传递的是地址,函数可以改变传入数组的值。
(2)一维数组
① 格式
下面两种格式其实都是一样的,按格式一书写的,编译器编译时会自动转化成格式二的形式
Ⅰ.格式一
直接在函数处定义一个一位数组来接收,更贴近于Java之类的高级语言
void fun(int p[])
Ⅱ.格式二
通过指针的方式作为形参
void fun(int *p)
② 示例
代码:
#include
void fun(int* p, int x) {
//p就是在这个函数内数组的指针
p[0] = 100;
//打印函数修改后的数组
for (int i = 0; i < x; i++)
{
printf("%d ", p[i]);
}
}
int main() {
//arr其实就是一个Int类型的指针
//所以传递的时候传的也是一个指针,所以当然要用指针来接收啦
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
fun(arr, 10);
//j打印查看原始数组是否被改变
printf("\n");
for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
{
printf("%d ", arr[i]);
}
return 0;
}
输出:
接着输出再强调一下,C语言中函数传数组传的是引用,函数内的更改会作用到主调处的数组
100 2 3 4 5 6 7 8 9 10
100 2 3 4 5 6 7 8 9 10
(3)二维数组
① 格式
传入数组的指针,p指针等价于主调函数数组的指针
void fun( int (*p)[4] )
② 示例
#include
//void fun( int (*p)[4] )
void fun(int p[][4]) {
p[0][1] = 86;
}
int main() {
int arr[3][4] = {
{1,2,3,4},
{2,4,6,7},
{6,3,1,9},
};
fun(arr);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4 ; j++) {
printf("%d ",arr[i][j]);
}
}
//1 86 3 4 2 4 6 7 6 3 1 9
return 0;
}
(4)指针数组
① 格式
传入指针的指针,q指针在函数内等价于主调函数指针数组的指针
void fun(char **q)
② 代码
#include
void fun(char** q) {
//q记录的是p的指针,等价于p[1]
//请注意,这里只是修改指针应用而已
q[1] = "I love you";
}
int main() {
char* p[4] = {"hello","doraemon","love","yes"};
//传入的p指针
fun(p);
printf("%s ",p[1]);
return 0;
}
3.函数返回指针
(1)前提
返回指针的时候,其指向的内存的内容不能释放。如果返回的指针指向的内容已经被释放了,那这个返回的地址就没有意义了
举例:
- 返回局部变量的指针,该指针指向的内容会在该局部变量所处函数结束后释放,所以这个指针是野指针,无意义
(2)可以返回的指针
- 静态关键字
static
修饰的直至 - 存储在文字常量区字符串的地址
- 存储在堆中的数据
(3)static的地址
① 概述
由static静态关键字修饰的变量,其数据只会被初始化一次,之后就会一直存储,所以,其指针指向的数据在程序运行期间是一直存在的,是有意义的指针
② 要点
给要返回的指针添加静态static
关键字
③ 示例
#include
char* fun() {
//使用static修饰字符串,使其值存入静态存储区
static char str[] = "HelloWorld!";
return str;
}
int main() {
char* str2;
str2 = fun();
printf("%s\n", str2);
return 0;
}
(4)文字常量区的地址
① 概述
文字常量区,通常存储在静态存储区,其内数据也是只会被初始化一次,之后就会一直存储,所以,其指针指向的数据在程序运行期间是一直存在的,是有意义的指针
② 格式
函数格式:
指定返回值的位置要填写相应类型的指针
char* fun()
{
char* str = "hello world";
return str;
}
③ 示例
#include
char* fun()
{
char* str = "hello world";
return str;
}
int main()
{
char* p;
p = fun();
printf("%s\n", p);//hello world
}
(5)堆内存中的地址
① 概述
堆内存中申请的空间,除非手动释放、程序结束,否则是会一直存在的,所以其直至所指向的地址值在程序运行期间是一直存在的,是有意义的指针
② 示例
#include
#include
#include
char* fun()
{
char* str;
str = (char*)malloc(100);
strcpy_s(str, 100, "hello world");
return str;
}
int main()
{
char* p;
p = fun();
printf("%s\n", p);//hello world
//释放堆数据
free(p);
}
4.函数指针
(1)概念
① 函数的指针
函数在程序运行时会先被加载到内存中,其首地址即函数名称、指针就是函数的入口
② 函数的调用
当我们调用函数的时候。CPU就会根据函数的指针,去内存中找指令并运行
(2)函数指针的用处
-
可以将其定义为函数的形参,使函数可以接收主调函数的函数,并在函数内运行该函数
例如:
int process(int(*p)(int,int)){}
- 传什么函数进process函数,就运行什么函数
-
...
(3)函数指针的定义
① 格式
- 参数不需要设置标识符,直接一个数据类型即可
指向函数的返回值类型(*指针变量名)(指向函数的形参列表)
② 例子
所能接收/指向的函数,必须满足函数指针所定义的格式
函数指针:int(*p)(int,int)
含义:定义了一个函数指针变量p,其指向的函数必须有一个整形的返回值,有两个整形参数
例如:
可接收的函数:
int max(int x,int y){
}
接收示例:p=max;
,直接将函数的名字(首地址)赋值给函数指针即可
(4)函数指针调用函数
① 概述
通过函数指针变量去调用函数
② 格式
(*函数指针变量名)(指向函数的形参)
③ 示例
#include
int max(int x, int y) {
int temp = 0;
if (x > y) {
temp = x;
}else {
temp = y;
}
return temp;
}
int main() {
int(*p)(int, int);
//将函数赋值给函数指针
p = max;
int num;
//通过指针调用函数
num = (*p)(8, 10);
printf("最大值是%d", num);
return 0;
}
(5)函数指针数组
① 概念
**概念:**函数指针数组是由若干个相同类型的函数指针变量构成的集合,在内存中连续的顺序存储,其每个元素都是一个函数指针变量
② 格式
Ⅰ.创建格式格式:
- 类型名:是指向函数的返回值类型
- 数组名:函数指针数组的名称
- 元素个数:这个函数指针数组所要存储的函数指针的数量
- 参数列表:指向函数的形参,不用指定标识符,直接用
,
分隔其形参数据类型即可
类型名(*数组名[元素个数])(形参列表)
例子:
函数指针数组:int(*p[5])(int,int);
这个函数指针数组,数组名是 p,有 5 个元素 p[0]~p[4],每个元素都是函数指针变量,每个函数指针变量指向的函数,必须有整型的返回值,两个整型参数。
Ⅱ.调用函数格式:
(*数组名[索引])(形参列表);
③ 例子
- 将max、min、add函数指针存入函数指针数组arr中
- 再通过函数指针数组arr调用min函数,求得最小值
#include
int max(int x, int y) {
int temp = 0;
if (x > y) {
temp = x;
}else {
temp = y;
}
return temp;
}
int min(int x, int y) {
int temp = 0;
if( x < y) {
temp = x;
}
else {
temp = y;
}
return temp;
}
int add(int x, int y) {
int temp = x + y;
return temp;
}
int main() {
//定义函数指针数组,用函数的名字来初始化数组
int(*arr[3])(int,int) = {max,min,add};
//调用函数指针数组里面的指针来调用其指向的函数
int num;
num = (*arr[2])(86, 10);
printf("%d", num);//返回96,成功调用min函数
return 0;
}
十一.特殊指针
1.通用指针
(1)概述
*void 通用指针:**任何类型的地址(指针)都可以给void*
类型的指针变量赋值,无需强制转换
(2)格式
void *
2.空指针
(1)概述
就是给指针赋值NULL
,一般用来给指针变量初始化