c指针
数据类型 *指针变量名;
int *p;//定义了一个指针变量p, * 是用来修饰变量的,说明p是个指针变量,变量名是p
在定义指针变量的时候 * 代表修饰的意思修饰p是个指针变量。
关于指针的运算符:&取地址 *取值
p=&a;//把a的地址给p赋值 & 是取地址符
eg: p=&a;//p保存了a的地址,也可以说p指向了a
int num;
num=*p;
在调用的时候*代表取值的意思, *p就相当于p指向的变量,即a
故num=*p和num=a的效果是一样的。
所以num的值为a的值
void* p;通用指针//可以保存任何类型的地址编号
无论什么类型的指针变量,在32(64)位系统下,都是4(8)个字节。(编译器也要是64位)
指针只能存放对应类型的变量的地址编号。
*指针变量 就相当于指针指向的变量
指针为整型指针取4个字节,为double型则取8个字节
字符指针则取一个字节
指针++ 指向下个对应类型的数据
字符指针++,指向下个字符数据,指针存放的地址编号加1
整型指针++,指向下个整型数据,指针存放的地址编号加4
数组元素的指针引用
指针名加下标
eg:
int a[6];
int *p;
p=a;//等价于p=&a[0]
p[2]=100;//等价于a[2]=100
c语言规定:数组的名字就是数组的首地址,即第0个元素的地址,就是&a[0]的地址,是个常量。
注意:p和a的不同,p是指针变量,而a是个常量。所以可以用等号给p赋值,但不能给a赋值。
eg:
p=&a[3];//正确
a=&a[3];//错误
通过指针变量运算加取值的方法来引用数组的元素
eg:
int a[5];
int *p;
p=a;
*(p+2)=100;//相当于a[2]=100,因为p=a为a的首地址p是第0个元素的地址,p+2是a[2]这个元素的地址。 对第二个元素的地址取值,即a[2]
通过数组名+取值的方法引用数组的元素
int a[5];
*(a+2)=100;//相当于a[2]=100;
注意:a+2是a[2]的地址,并没有给a赋值。
指针的运算
1.指针可以加一个整数,往下指几个它指向的变量,结果还是个地址。
int a[5];
int *p;
p=a;
p+2;//p是a[0]的地址,p+2是&a[2]
假如p保存的地址编号是2000的话,p+2代表的地址编号是2008(int 类型占4个字节sizeof(int))
char buf[5];
char *q;
q=buf;
q+2//相当于&buf[2]
假如q中存放的地址编号是2000的话,q+2代表的地址编号是2002(char 类型占1个字节sizeof(char))
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;
int value1 = p[2];
int value2 = *(p + 2);
printf("%d\n", value1); // 输出 30
printf("%d\n", value2); // 输出 30
2.两个相同类型指针可以比较大小
两个相同类型的指针指向同一个数组的元素的时候,比较大小才有意义。指向前面元素的指针 小于 指向后面元素的指针
int a[10];
int *p,*q;
p=&a[1];
q=&a[6];
if(p>q)
printf("p大");
else if(p<q)
printf("q大");
else
printf("都大");
3.两个相同类型的指针可以做减法
必须是两个相同类型的指针指向同一个数组的元素的时候,做减法才有意义。做减法的结果是,两个指针指向的中间有多少个元素。
int a[10];
int *p, *q;
p = &a[3];
q = &a[6];
if (p > q)
printf("p大%d",p-q);
else if (p < q)
printf("q大%d",q-p);
else
printf("都大");
4.两个相同类型的指针可以相互赋值
只有相同类型的指针才可以相互赋值(void*类型的除外)
int *p;
int *q;
int a;
p=&a;//p保存a的地址,p指向了变量a
q=p;//用p给q赋值,q也保存了a的地址,指向a
//注意:类型不相同的指针想要相互赋值,必须进行强制类型转换
指针数组
在 C 语言中,指针数组是一个包含多个指针元素的数组。每个指针元素可以指向不同类型的数据或相同类型的数据。
要声明和使用指针数组,可以按照以下步骤进行操作:
-
声明指针数组:在声明时,使用指针声明符 来指示数组元素是指针类型。
*int* ptrArray[5]; // 声明一个包含 5 个整数指针的指针数组 -
初始化指针数组:可以逐个初始化每个指针元素,也可以使用循环进行批量初始化。
int a = 10, b = 20, c = 30;
int* ptrArray[3] = { &a, &b, &c }; // 初始化一个包含 3 个整数指针的指针数组 -
使用指针数组:通过索引访问指针数组中的各个指针元素,并将其用于操作对应的数据。
int* ptr = ptrArray[0]; // 获取第一个指针元素
int value = *ptr; // 获取第一个指针元素所指向的整数数据
printf("%d\n", value); // 输出 10
指针数组常用于需要存储多个指针的情况,例如在动态分配内存、处理字符串数组、实现多级指针等方面。
请注意,在使用指针数组时,确保指针元素指向的内存是有效的,并避免访问已释放的内存或悬空指针,以避免出现未定义行为。
指针的指针
指针的指针是指在 C 语言中,可以通过一个指向指针的指针变量来操作指针本身的概念。它允许我们通过间接引用的方式修改指针变量所指向的内存地址。
在 C 语言中,通过使用两个星号()来声明指向指针的指针变量。下面是一个示例:**
int main() {
int value = 10;
int* ptr = &value; // 指向整数的指针
int** ptr_ptr = &ptr; // 指向指针的指针
printf("Value: %d\n", **ptr_ptr); // 间接引用获取值
return 0;
}
字符串和指针
字符串的概念:
字符串就是以'\0'结尾的若干字符的集合:例如"hello world".
字符串的地址,是第一个字符的地址。如:字符串"hello world" 的地址,就是字符串中字符'h'的地址。
定义一个字符串指针变量保存字符串的地址,比如:char *s="hello world"
这个字符不是存放在s指针变量中的,s只是存放字符'h'的地址编号。"hello world"存放在文字常量区。
字符串的可修改性:
字符串内容是否可以修改,取决于字符串存放在哪里。
1.存放在数组中的字符串的内容是可以修改的
char str[100]="I love C!"
str[0]='y';//可以修改
注:数组没有用const修饰
2.存放在文字常量区里的
内容是不可以修改的
char* str="I love C!"
*str = 'y';//错误,I存放在文字常量区,不可更改
注:
str指向文字常量区时,它指向的内存的内容不可被修改。
str是指针变量可以指向别的地方,即可以给str重新赋值,让它指向别的地方。
//eg:
char *s="Y love C1";
char *str="I love C!";
printf("%s \n",str);
str=s;
printf("%s",str);
3.堆区的内容是可以被修改的
char *str = (char)malloc(10);
strcpy(str,"I love C!");
*str = 'Y';//可以,堆区内容是可修改的
注:
str指向堆区的时候,str指向的内存内容是可以被修改的
str是指针变量,也可以指向别的地方。即可以给str重新赋值,让它指向别的地方
注意:str指针指向的内存能不能被修改,要看str指向哪里。
str指向文字常量区时。内存内容不可修改。
str指向数组(非const修饰)、堆区时,它指向内存的内容是可以修改的
初始化:
1.字符数组初始化:
char buf_aver[20]="hello world";
2.指针指向文字常量区,初始化:
char buf_point="hello world";
3.指针指向堆区,堆区存放字符串不能初始化,只能先给指针赋值,让指针指向堆区,再使用strcpy、scanf等方法把字符串拷贝到堆区。
char *buf_heap;
buf_heap=(char *)malloc(15);
strcpy(buf_heap,"hello world");
scanf("%s",buf_heap);
使用时赋值:
1.字符数组:使用scanf,strcpy等。
char buf[20]="hello world";
buf = "hell kitty";//错误,字符数组名是个常量,不能用等号给常量赋值。
在 C 语言中,字符数组名是一个常量指针,指向字符数组的首地址,因此不能直接使用赋值运算符来更改字符数组的值。
2.指针指向文字常量区
char *buf_point="hello world";
buf_point="hello kitty";//正确,buf_point指向另一个字符串
strcpy(buf_point,"hello kitty");//错误,buf_point指向的是文字常量区,内容只读。当指针指向文字常量区时,不能通过指针修改文字常量区的内容。
3.指针指向堆区,堆区存放字符串
char *buf_heap;
buf_heap=(char *)malloc(15);
strcpy(buf_heap,"hello world");
scanf("%s",buf_heap);
数组指针
数组名是数组的首地址,是第0个元素的地址,是个常量,数组名+1指向下个元素。
二维数组a中,a+1指向下一个一维数组,即下一行。
int a[3][5];
printf("a=%p\n",a);
printf("a+1=%p\n",a+1);
//输出:
a=000000000061FDE0
a+1=000000000061FDF4
F4-E0=14HX=20=sizeof(int)*5
数组指针本身是个指针,指向一个数组,加1跳一个数组,即指向下个数组。
数组指针的定义方法:
指向的数组的类型 (*指针变量名)[指向数组元素的个数]
int (*p)[5];//定义了一个数组指针变量p,p指向的是整型的有5个元素的数组。p+1往下指5个整型,跳过一个有5个整型元素的数组。
int a[3][5];
int (*p)[5];
p=a;
printf("a=%p\n",p);
printf("a+1=%p\n",p+1);
//一般一维数组指针配合二维数组用,一维数组指针的个数必须等于二维数组的列数
void fun(int (*p)[5], int x, int y);
int main()
{
int a[3][5] = {
{1, 2, 3, 4, 5},
{6, 7, 8, 9, 0},
{11, 12, 13, 14, 15}};
printf("a[1][2]=%d\n", a[1][2]);
fun(a, 3, 5);
printf("a[1][2]=%d\n", a[1][2]);
return 0;
}
void fun(int (*p)[5], int x, int y)
{
p[1][2] = 100;
}
//二维数组传参要用一维数组指针接收并且一维数组指针的个数必须等于二维数组的列数
二维数组指针,加1后指向下个二维数组
int(*p)[4] [5]
配合三维数组来用,三维数组中由若干个4行5列二维数组构成
int a[3] [4] [5]
注意:
容易混淆的概念:
1.指针数组:是个数组,由若干个相同类型的指针构成的集合。
int *p[10];
数组p由10个int *类型的指针变量构成p[0]~p[9]
2.数组指针:是个指针,指向一个数组,加1跳一个数组。
int(*p)[10];
p是个数组指针,p+1指向下个数组,跳10个整型。
3.指针的指针:
int **p;//p是指针的指针又称二级指针
int *q;
p=&q;
数组名取地址:变成 数组指针
一维数组名取地址,变成一维数组指针,即加1跳一个一维数组。
int a[10];
a+1 跳一个整型元素,是a[1]的地址
a和a+1相差一个元素,4个字节。
&a就变成了一个一维数组指针,是int(*p)[10]类型的。
a是int (*)类型的指针,是a[0]的地址
(&a)+1 和 &a相差一个数组即10个元素即40个字节。
a和&a 打印出的地址值是相同的,因为它们都指向了数组 a 的起始位置。但是它们的类型是不同的,a 的类型是int* ,而&a的类型是int(*)[10] 。
所以,总结起来:
a 是指向一维数组第一个元素的指针。
&a 是指向整个一维数组的指针。
数组名和指针变量的区别
int a[5];
int *p;
p=a;
//相同点:
a是数组的名字,是a[0]的地址,p=a即p保存了a[0]的地址,即a和p都指向a[0],所以在引用数组元素的时候,a和p等价。
a[0],*(a+2),p[2],*(p+2)都是对数组中a中a[2]元素的引用。
//不同点:
a是常量p是变量
可以用等号'='给p赋值,但不能给a赋值
对a取地址和对p取地址结果不同
因为a是数组的名字,所以对a取地址结果为数组指针。
p是个指针变量,所以对p取地址&p结果为指针的指针。
数组指针取*
数组指针取*,并不是取值的意思,而是指针的类型发生变化:
一维数组指针取*,结果为它指向的一维数组第0个元素的地址,它们还是指向同一个地方。
二维数组指针取*,结果为一维数组指针,它们还是指向同一个地方。
三维数组指针取*,结果为二维数组指针,它们还是指向同一个地方。
多维以此类推
int a[3][5];
int (*p)[5];
p = a;
printf("a=%p\n", a);
printf("*a=%p\n", *a);
printf("*a+1=%p\n", *(a + 1));
这段代码展示了在C语言中使用指针访问二维数组中的元素。下面是每行代码的作用:
int a[3][5];:声明一个包含3行5列整数的二维数组。
int (*p)[5];:声明一个指向包含5个整数的数组的指针。
p = a;:将指针p赋值为数组a的第一个元素的地址,等价于&a[0]。
printf("a=%p\n", a);:打印数组a的第一个元素的地址。
printf("*a=%p\n", *a);:打印a的第一行中的第一个元素的地址,等价于&a[0][0]。
printf("*a+1=%p\n", *(a + 1));:打印a的第二行中的第一个元素的地址,等价于&a[1][0]。这是通过将指针*a(指向a的第一行的第一个元素)加1来实现的。
需要注意的是,当对一个指向二维数组的指针使用解引用运算符*时,得到的是指向数组的第一行第一个元素的指针。这就是为什么*a等价于&a[0][0]。当将一个整数添加到数组的指针上时,指针会向前移动该整数个数组元素。因此,*(a + 1)等价于&a[1][0]。
printf("*a+1=%p\n", *a + 1);
printf("*a+1=%p\n", *a + 1);:打印a的第一行中的第二个元素的地址,等价于&a[0][1]。这是通过将指向a的第一行的第一个元素的指针解引用并加1来实现的。
需要注意的是,当将一个整数添加到指向数组的指针上时,指针会向前移动该整数个数组元素。因此,*a + 1等价于&a[0][1]。
指针和函数的关系
指针作为函数的参数
就是给函数传一个地址
eg:
int num=0;
scanf("%d",&num);
函数传参:
传数值:
void swap(int x, int y)//x,y是形参
{
int temp;
temp = x;
x = y;
y = temp;
printf("x=%d,y=%d\n", x, y);
}
int main()
{
int a = 10;
int b = 20;
swap(a, b);//a,b是实参
printf("a=%d,b=%d\n", a, b);
return 0;
}
输出:
x=20,y=10
a=10,b=20
结论:
给被调函数传数值,只能改变被调函数形参的值,不能改变主调函数实参的值。
传地址:
void swap(int *x, int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
printf("x=%d,y=%d\n", *x, *y);
}
int main()
{
int a = 10;
int b = 20;
swap(&a, &b);
printf("a=%d,b=%d\n", a, b);
return 0;
}
结果:
x=20,y=10
a=20,b=10
结论:
调用函数的时候传变量的地址,在被调函数中通过*地址来改变主调函数中的变量的值。
void swap(int *x, int *y)
{
int *temp;
temp = x;
x = y;
y = temp;
printf("x=%d,y=%d\n", *x, *y);
}
int main()
{
int a = 10;
int b = 20;
swap(&a, &b);
printf("a=%d,b=%d\n", a, b);
return 0;
}
结果:
x=20,y=10
a=10,b=20
总结:
要想改变主调函数中变量的值,必须传变量的地址,而且还得通过*地址去赋值。
void fun(char **q)
{
*q = "hello kitty";
printf("q=%s\n", *q);
}
int main()
{
char *p = "hello world";
printf("p=%s\n", p);
fun(&p);
printf("p=%s\n", p);
return 0;
}
/*
p=hello world
q=hello kitty
p=hello kitty*/
给函数传数组:
给函数传数组的时候,没法一下将数组的内容作为整体传进去。
只能传数组名进去,数组名就是数组的首地址,即只能把数组的地址传进去。
传一维数组:
//void fun(int p[])
void fun(int *p)
{
printf("p[2]=%d\n", *(p+2));
*(p+3)=100;
}
int main()
{
int a[10]={1,2,3,4,5,6,7,8,9,0};
fun(a);
printf("a[3]=%d\n", *(a+3));
printf("a[3]=%d\n", a[3]);
return 0;
}
void fun(int p[]):此声明等效于void fun(int *p) 因为数组参数会自动转换为指向其第一个元素的指针。
传二维数组:
// void fun(int p[][4])
void fun(int (*p)[4])
{
printf("p[1][2]=%d\n", p[1][2]);
p[0][1] = 100;
}
int main()
{
int a[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}};
fun(a);
printf("a[0][1]=%d\n", a[0][1]);
return 0;
}
void fun(int p[][4]):此声明等效于void fun(int (*p)[4]) 因为数组的第一维被忽略,而第二维被指定为指向 4 个整数的数组的指针。
传指针数组:
// void fun(char *q[],int x)
void fun(char **q, int x)
{
for (int i = 0; i < x; i++)
{
printf("p[%d]=%s\n", i, q[i]);
}
}
int main()
{
char *p[3] = {"hello", "world", "kitty"}; // p[0] p[1] p[2]是 char*
printf("%c\n", *p[0]);
printf("%s\n", p[0]);
fun(p, 3); // p就是&p[0],是char**
return 0;
}
输出:
h
hello
p[0]=hello
p[1]=world
p[2]=kitty
当我们访问时p[0],我们得到字符串第一个字符的地址"hello".当我们使用*运算符,我们得到字符串第一个字符的值,即字符'h'.因此*p[0]等于字符'h',而不是字符串"hello".
指针作为函数的返回值
char *fun()
{
char str[100] = "hello world";
return str;
}
int main()
{
char *p;
p = fun();
printf("p=%s\n", p);
return 0;
}
在fun()函数退出,内存分配给str被释放,并且指向的指针str变得无效。因此,访问指向的内存p在main()函数之后fun()完成将导致未定义的行为.
若要解决此问题,可以使用以下方法动态分配内存调用函数然后应该释放分配的内存fun().
char *fun() {
char *str = malloc(100);
strcpy(str, "hello world");
return str;
}
int main() {
char *p;
p = fun();
printf("p=%s\n", p);
free(p); // free the memory allocated by fun()
return 0;
}
在此修改后的代码中,strcpy().然后,它返回指向已分配内存的指针。在main()函数,使用指向的字符串后p,则使用 释放分配的内存free().
//总结:返回地址的时候,地址指向的内存的内容不能释放。
如果返回的指针指向的内容已经被释放了,返回这个地址,也没有意义了。
解决方法一:返回静态局部数组的地址,因为静态数组的内容在函数调用结束后依然存在
static char str[100]="hello world";
使用静态变量:将str声明为静态变量,这样它将在整个程序的生命周期内存在。但是请记住,静态变量在多线程环境中可能会导致问题。
解决方法二:
返回文字常量区的字符串的地址,因为文字常量区的内容一直存在。
char *fun()
{
char *str= "hello world";
return str;
}
int main()
{
char *p;
p = fun();
printf("p=%s\n", p);
return 0;
}
解决方法三:
返回堆内存的地址,因为堆区的内容一直存在,直到free才释放。
char *fun()
{
char *str;
str = (char *)malloc(100);
strcpy(str, "hello world");
return str;
}
int main()
{
char *p;
p = fun();
printf("p=%s\n", p);
free(p);
return 0;
}
指针保存函数的地址(函数指针)
1.函数指针的概念:
函数在运行程序的时候会将函数的指令加载到内存的代码段,所以函数也有起始地址。
c语言规定:函数的名字就是函数的首地址,即函数的入口地址,这样就可以定义一个指针变量,来存放函数的地址。这个指针变量就是函数指针变量。
2.函数指针的用处:
函数指针用来保存函数的入口地址。
在项目开发中,我们经常需要编写或者调用带函数指针参数的函数。
比如Linux系统中创建多线程的函数,它有个参数就是函数指针,接收线程函数的入口地址.
函数指针变量的定义
返回值类型(*函数指针变量名)(形参列表);
需要注意的是,函数指针变量名要使用括号将*与变量名括起来,这是因为在C语言中,*的优先级比()低,如果不使用括号,编译器会将该声明解释为返回值为指针类型的函数,而不是指向函数的指针。
int add(int a, int b) {
return a + b;
}
int main() {
int (*ptr)(int, int); // 定义一个函数指针变量 ptr
ptr = add; // 将函数 add 的地址赋给指针 ptr
int result = ptr(2, 3); // 使用指针调用函数
printf("Result: %d\n", result);
return 0;
}
调用函数的方法
1.通过函数名调用函数(常用)
2.通过函数指针变量调用
int add(int a, int b)
{
return a + b;
}
int main()
{
int (*ptr)(int a, int b); // 定义一个函数指针变量 ptr
ptr = add; // 将函数 add 的地址赋给指针 ptr
int result = (*ptr)(3, 6); // 使用指针调用函数
printf("Result: %d\n", result);
result = ptr(6, 6); // 使用指针调用函数
printf("Result: %d\n", result);
return 0;
}
(*ptr)(3, 6);和 ptr(6, 6);这两种方式是等效的,都可以用来调用函数
函数指针数组
由若干个相同类型的函数指针变量构成的集合,在内存中连续的顺序存储。
函数指针数组是个数组,它的每个元素都是一个函数指针变量。
函数指针数组的定义:
类型名(*数组名[元素个数])(形参列表)
//eg:int(*p[5])(int,int);定义了一个函数指针数组,有5个元素p[0]~p[4],每个元素都是函数指针变量,其指向的函数必须有整型的返回值,两个整型参数。
int add(int a, int b)
{
return a + b;
}
int max(int x,int y)
{
int temp;
if(x>y)
temp=x;
else
temp=y;
return temp;
}
int min(int x,int y)
{
int temp;
if(x<y)
temp=x;
else
temp=y;
return temp;
}
int main()
{
int (*p[3])(int,int)={min,max,add};
int a=(*p[1])(1,2);
printf("%d\n",a);
a=p[2](2,3);
printf("%d\n",a);
return 0;
}
输出:
2
5
(*p[1])(1,2); 和 p[2](1,2);等效,都可用来调用函数
经常容易混淆的指针概念
第一组: 1.int *a[10];
这是个指针数组,数组a中有10个整型的指针变量a[0]~a[9],每个元素都是 int*类型的指针变量
2.int (*a)[10];
数组指针变量,存地址编号,它指向一个数组。+1指向下个数组。
3.int **p;
指针的指针,保存指针变量的地址。
int **p;
int *q;
p=&q;
/**/
int **p;
int *q[10];
p=&q[0];//等价于p=q;
第二组: 1.int *f();
声明这个函数返回值为int*类型
2.int(*f)();
是个函数指针变量,存放函数的地址
特殊指针
1.空类型指针(void*)
void*通用指针,任何类型的地址都可以给void *类型的指针变量赋值。
int *p;;
void *q;
q=p;是可以的,不用强制类型转换
2.NULL
空指针:
char *p=NULL;
可以认为p哪里都不指向,也可以认为p指向内存编号为0的存储单位。一般NULL用在给指针变量初始化。
main函数传参:
int main(int argc,char* argv[])
{
printf("argc=%d\n",argc);
for(int i=0;i<argc;i++)
{
printf("argv[%d]=%s\n",i,argv[i]);
}
}
//CMD终端运行
c:\Users\r7ftf\Desktop\Ctest>dir
驱动器 C 中的卷没有标签。
卷的序列号是 1EAF-0AE7
c:\Users\r7ftf\Desktop\Ctest 的目录
2023/07/25 18:30 <DIR> .
2023/07/25 18:30 <DIR> ..
2023/07/08 10:54 <DIR> .vscode
2023/07/18 19:37 510 a.c
2023/07/18 19:36 54,716 a.exe
2023/07/25 18:30 219 sz.c
2023/07/25 18:30 54,024 sz.exe
2023/07/14 12:26 1,656 tat1.c
2023/07/12 20:48 58,850 tat1.exe
2023/07/24 12:02 54,063 tempCodeRunnerFile.exe
2023/07/08 11:46 646 test.c
2023/07/08 11:46 55,063 test.exe
9 个文件 279,747 字节
3 个目录 15,230,459,904 可用字节
c:\Users\r7ftf\Desktop\Ctest>sz.exe aaa bbb ccc ddd eee
argc=6
argv[0]=sz.exe
argv[1]=aaa
argv[2]=