c指针的学习

基础知识

c的指针指向的是某个内存地址。

指针变量的定义

int *p;
char *p;

说明:
1、指针变量存放的是地址类型。
2、定义指针变量的时候设定的变量类型,确定的是从指针存的首地址开始后面读取多少个字节。(int 为4个,char为1个),而指针变量的大小是固定的,大小看设备,一般是4个字节(32位)。

	int a = 51;
	int* p = &a;

	printf("%d", sizeof(p));

结果:

4

和指针有关的运算符

运算符作用
*定义指针时使用,和取值时一个符号
&取址运算符
*取值运算符
*	//定义指针时使用,和取值时一个符号
&	//取址运算符
*	//取值运算符
/*举例*/
int *p=&a; 			//定义指针和取址
printf("%d",*p);	//取值

说明:
1、当需要读取某个变量的地址时,使用取址运算符
2、当对指针变量操作,需要读取指针所指向的变量的值时,使用取值运算符

简单的用法

#include <stdio.h>

int main (void)
{
	int a=0;
	char b ='A';
	int * ap=&a;
	char * bp=&b;
	printf("%d\n",a);
	printf("%d\n",*ap); 
	printf("%c\n",b);
	printf("%c\n",*ab); 
}

结果:

0
0
A
A

注意:指针变量一定要初始化,不能对未初始化的指针进行操作

void指针和NULL指针

void指针

void指针代表的是无类型,一般使用时不能用来定义变量,但是可以定义指针变量。

    int a;
    void b;//报错
    void *c;//可以正常定义

结果:

严重性 代码 说明 项目 文件 行 禁止显示状态 错误 C2182 “b”: 非法使用“void”类型
严重性 代码 说明 项目 文件 行 禁止显示状态 错误(活动) E0070 不允许使用不完整的类型

○那么void指针是怎么使用的?

  1. void类型的指针变量可以指向任意类型的指针,但是不能对其直接解引用,因为程序不知道具体的数据大小。
  2. 如需对其解引用,可以只用强制转换(*)将void指针变量转换为正确的指针变量。
  3. 比较特殊的是char类型的字符串,因为char[]打印原理是从其实地址打印一直打印到\0结束,所以看上去void类型指针指向字符串时,似乎可以打印成果。但是这是极不规范的写法,并且也是错误的。所以一般情况也需要对其进行强制转换。
int main(void)
{
    int a=10;
    void *c;//可以正常定义

    c = &a;
    printf("a的地址:%p\n", &a);
    printf("*c的内容:%p\n", c);
    printf("对void *c解引用:%d\n", *((int *)c));
    return 0;
}

结果:

a的地址:0028FD38
*c的内容:0028FD38
对void *c解引用:10

NULL指针

NULL在c语言中是一个宏定义:#define NULL ((void *)0) 表示指向一个不被使用的地址。
NULL指针的作用:当编写程序的时候,定义的指针没有确定具体指向的地址的时候,可以使其等于NULL,这是一个良好的使用习惯。

int main(void)
{
    int* P1;
    int* P2=NULL;

    printf("P1的内容:%p\n", P1);
    printf("P2的内容:%p \n", P2);
    return 0;
}

结果:

严重性 代码 说明 项目 文件 行 禁止显示状态 错误 C4700 使用了未初始化的局部变量“P1”

为什么要使用NULL指针?

  1. 当指针没有初始化时,他会随机指向一个地址,这是没有意义的。如果你在程序中不小心使用了该指针,会发生意想不到的问题。当然这个在编译的时候就会报错。
  2. 当不知道指针变量该指向谁的时候,可以先指向NULL。这样来避免该指针变成野指针(迷途指针)
  3. 这是一个很重要的变成习惯。

指向指针的指针

当使用非指针变量(如int)定义时,储存的数据是一个数(如int存储的是整型数据)。
而使用指针变量定义时,存储数据认定为是一个地址。

那么对比下面两种定义方式。

int a = 10;
int *p1 = &a;
int *p2= &p1;

int a = 10;
int *p1 = &a;
int **p2= &p1;

考虑输出: printf(“a的内容:%p\n”, **p2);
第一种:因为p2是指针变量,第一次解引用,我们得到的是p1的地址。注意,此时程序认为p1地址下存储的数据是一个数,而不是一个地址,所以当我们需要第二次解引用读取p1存储的a地址下的数据时,程序时会报错。因为不能对一个数进行解引用。

第二种:因为p2是指向指针的指针变量,第一次解引用,我们得到的是p1的地址。此时程序认为p1的数据类型是一个指针,所以p1地址下存储的是一个地址。当我们第二次解引用读取p1存储的a地址下的数据时,程序会读取a所在内存地址中存储的值。

总的来说,当需要指针指向一个指针的时候,要声明指向的数据类型是一个指针类型。否则,地址会被认定为仅仅是一个数。

指针和数组的关系

**明确:**一个指针变量,在存放在某个内存下,其存放内容是一个变量的地址。
不管这个变量是整形、数组还是字符串,他存放的永远存放的首地址。
例如:
int a=1 为4个字节其存放在地址
0xXXXXXX100000000
0xXXXXXX200000000
0xXXXXXX300000000
0xXXXXXX400000001
那么对于指向a的指针p,它在其他内存上开辟4个字节,并只存放0xXXXXXX1这个地址。因为我们定义指针p是int类型的,所以它会在起始位置自动后面数4个字节。

知道了这个关系,我们也就很好理解指针和数组的关系

指针和数组的例子

int main(void)
{
	char* p;
	char str[128];

	/*之前的操作*/
	printf("输入数组");
	scanf("%s", str);
	printf("数组是:%s\n", str);

	/*验证第一个  数组名 就是 数组第一个元素 的地址*/
	printf("str: %p\n", str);
	printf("&str[0]: %p\n", &str[0]);

	/*如何用一个指针指向数组*/
	p = str;//方法一
	//p = &str[0];//方法二

	printf("%s\n", p);
	printf("%c\n", *p);
	printf("%c\n", *(p+1));

	/*可以用数组下标的方法调用指针,也可以用指针的方法对数组进行调用*/
	printf("%c\n", p[0]);
	printf("%c\n", p[1]);

	printf("%c\n", *str);
	printf("%c\n", *(str + 1));
	return 0

结果:

输入数组ab
数组是:ab
str: 00F5F76C
&str[0]: 00F5F76C
ab
a
b
a
b
a
b

指针和数组的区别

需要注意:数组和指针虽然很相似,但是是不一样的
我们在上面进行了说明,数组名就是数组第一个元素的地址。但是,这种说法也不是完全正确的
首先我们先简单解释:
左值和右值:左值一般是指赋值符号左边的值,一般情况下是不能被修改的。不是左值的值叫做右值
此时,我们就能发现指针和数组的不同了,举个例子:

int main(void)
{
	char* p;
	char str[128] = {"ABCDEFGHIGK"};
	int total = 0;

		while(*(str)++ != '\0')
		{
			total++;
		}
	return 0;
}

结果:

严重性	代码	说明	项目	文件	行	禁止显示状态
错误(活动)	E0137	表达式必须是可修改的左值	Project2	

可以看到,作为数组名称的str为左值,不能进行修改。那么我们应该怎么做呢?
修改如下:

int main(void)
{
	char* p;
	char str[128] = {"ABCDEFGHIGK"};
	p = str;
	int total = 0;

		while(*p++ != '\0')
		{
			total++;
		}
		printf("%d", total);
	return 0;
}

结果:

11

你会发现数组的函数名作为一个左值,不能在某些情况下使用。

指针数组和数组指针

说明:判断一个表达到底是什么,只要看最后是什么。如 指针数组,它结尾是数组,所以这个表达的本质就是一个数组。

指针数组

int *p[5]; 理解为:有一个数组,数组里的每一个元素都是指向int类型的指针。

int main(void)
{
	int a = 1;
	int b = 2;
	int c = 3;
	int d = 4;
	int e = 5;

	int *p[5] = { &a,&b,&c,&d,&e };

	for (size_t i = 0; i < 5; i++)
	{
		printf("%p\n", &p[i]);
	}
	printf("%p\n", &a);
}

数组指针

int (*p)[5]; 理解为:首先(*p)确定它为一个指针,指向的是有5个元素的数组

int main(void)
{
	int str[5] = { 1,2,3,4,5 };
	int(*a)[5] = &str;

	for (size_t i = 0; i < 5; i++)
	{
		printf("%d\n", *(*a+i));
	}
}

这里比较难理解。
首先当定义数组指针的时候,int(*a)[5] = &str;这个指针相当于把数组看作了一个整体,所以在赋值的时候,要对该数组取地址。
其次,在打印数组中的元素时,*(*a+i)首先需要先解引用一次,找到数组所在的地址,然后再解引用,调出数组用对应元素的地址。

指针和二维数组

首先我们先看一个二维数组array[4][5]
array[0][0]——>datadata data data data
array[1][0]——>datadata data data data
array[2][0]——>datadata data data data
array[3][0]——>datadata data data data
需要理解二位数组存是怎么存放的。
定义:char array[4][5] = { "ABCD","EFGH","IJKL","MNOP" };

array[0]0x00000000Aarray[1]0x00000005Earray[2]0x0000000AIarray[3]0x000000FM
0x00000001B0x00000006F0x0000000BJ0x00000010N
0x00000002C0x00000007G0x0000000CK0x00000011O
0x00000003D0x00000008H0x0000000DL0x00000012P
0x00000004\n0x00000009\n0x0000000E\n0x00000013\n

下面对几种表达进行说明:

  1. array[0]代表了什么:代表了二维数组第一行的的首地址。同理array[1]代表了第二行的首地址。
  2. *(array+1)代表了什么:array+1表示指向array的下一行(里面存放的是array[1]的地址),进行解引用得到array[1][0]的地址。
  3. 具体内容看另一篇文章:指针和二维数组

指向指针的指针和二维数组

指向指针的指针是不能去表示二维数组的。

int main(void)
{
    int str[3][4] = { {123},{456},{789} };
    int** p = str;

    printf("str+1的地址:%p\n",str+1);
    printf("p+1的地址:%p\n", p + 1);
    return 0;
}

结果:

str+1的地址:0048FDC0
p+1的地址:0048FDB4

你会发现地址是不一样的,这是由于对于指向指针的指针p来说,他的长度只是一个int的长度,而对于二维数组,str+1的长度,是整一列。所以当你希望用指向指针的指针操作二维数组时,就无法正确操作。

那么我们需要怎么去处理呢?
使用数组指针

int main(void)
{
    int str[3][4] = { {123},{456},{789} };
    int (*p)[4] = str;

    printf("str+1的地址:%p\n",str+1);
    printf("p+1的地址:%p\n", p + 1);
    return 0;
}

结果:

str+1的地址:001BFA2C
p+1的地址:001BFA2C

常量与指针

在c语言中,我们怎么去定义常量?

  1. 使用#define a 10宏定义去实现
  2. 使用const int a = 10;来初始化变量

当然const是能修饰指针的。那么在定义变量的时候会有一下几种格式。
在这里插入图片描述

  1. 对于part1,不使用const进行修饰,我们可以在程序中随意的改变变量的值。
  2. 对于part2, 用const修饰过之后,不管是普通的变量还是指针,都不能进行更改。
  3. 对于part3,当使用const int *p1对指针修饰时,其代表该指针指向的数据是const类型,所以不能对指针指向的地址中的内容进行更改。但是指针本身不是const的,所以指针可以更换指向的目标。
  4. 对于part4,此时使用const int * const p1,其表示指针本身不能被修改,指针指向的地址中的数据也不能被更改。
  5. 当然还有int *const p3 =&a这种表达,其代表指针指代的地址不能更改,但是指向的地址中的内容可以进行更改。

他们的称呼:

表达称呼
const int a常量
const int *p1指向常量的指针
int *const p1常量指针
const int *const p1指向常量的常量指针

更为复杂的:指向常量的常量指针的指针 const int *const *p
其表达的意思是指针变量p指向的是一个常量指针,这个常量指针指向的地址下存放的是一个常量。
总的来说,确定好const修饰的地方,就可以很好的进行判断程序的结构。

指针和函数

函数

函数的基本结构是:

类型名 函数名(参数列表)
{
	函数体;
}

对于函数,在程序开头,还要进行声明。
声明格式:

类型名 函数名(参数列表)

此时除了函数名,其他的定义名称都可以省略,只需要保留数据的类型即可。

传值和传址

在调用函数时,输入的参数叫做实参,而在函数体参数列表中的数据叫做形参
当形参定义的是普通的变量的时候,实参也应该传递普通的变量,这个过程叫做传值。
当形参定义的是指针变量的时候,实参应该传递的是一个地址,此时的过程叫做传址。

指针函数和函数指针

  • 指针函数:int *p(){}:这是一个函数,返回值是一个指针。
  • 函数指针:int (*p)( ){}:这是一个指针,其指向的地址是某个函数的返回值。

在实际使用时,可以进行各种嵌套,比如函数指针作为函数的参数等等。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
学习C语言指针需要掌握以下几个方面: 1.理解指针的概念和作用:指针C语言中非常重要的概念,它可以让我们直接访问内存中的数据,从而实现更高效的编程。因此,我们需要理解指针的概念和作用,以及指针和变量之间的关系。 2.掌握指针的基本语法:学习指针需要掌握指针的基本语法,包括指针的声明、指针的初始化、指针的运算等。 3.了解指针和数组的关系:在C语言中,数组和指针有着密切的关系,因此我们需要了解指针和数组之间的关系,以及如何使用指针来操作数组。 4.熟悉指针和函数的关系:指针和函数也有着密切的关系,我们需要了解如何使用指针来传递参数、返回值等。 5.掌握指针的高级应用:指针C语言中有着广泛的应用,包括动态内存分配、指针数组、指向指针指针等高级应用,我们需要掌握这些高级应用,以便更好地应对实际编程中的问题。 以下是一些学习C语言指针的资源推荐: 1.《C和指针》(中文版):这是一本非常经典的C语言指针教材,适合初学者和进阶者阅读。 2.《C Primer Plus》(英文版):这是一本非常全面的C语言教材,其中包括了大量的指针相关内容。 3.网上的C语言指针教程和视频教程:网上有很多免费的C语言指针教程和视频教程,可以帮助我们更好地学习和理解指针

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值