C语言学习笔记之指针

主题:C语言
时间:2021年1月14日20:55:20
作者:ybb

C语言学习笔记之指针

9.1形象的理解指针的基本概念:

地址/指针:内存中字节的编号
%#X会以带0x前缀的16进制输出

C语言2进制数、8进制数、16进制数的格式输出
C语言2进制数、8进制数、16进制数的格式输出
short int long
ho o lo
hd d ld
hx x lx

linux一切都是文件,二C语言一切都是地址,指针可以指向数据存储的地方,所以指针在C语言地位第一。
数据和代码都以2进制的形式存储在内存中,计算机无法从格式上区分模块内存存储的到底是数据还是代码;
当程序被加载到内存后,操作系统会给不同的内存块指定不同的权限,
通过看内存块拥有的权限可以区分代码和数据;
代码;拥有读取和执行权限
数据:拥有读取权限、写入权限。

CPU通过地址取得内存中的代码和数据,程序在执行过程中会告知CPU要执行的代码以及要读写的数据的地址。

变量名和函数名为我们提供了方便,让我们在编写代码的过程中可以使用易于阅读和理解的英文字符串,不用直接面对二进制地址,那场景简直让人崩溃。
需要注意的是,虽然变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符,但在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名、字符串名和数组名表示的是代码块或数据块的首地址。

简单的说,我们在编程的时候,就默认变量名表示的就是数据本身,但是函数名、数组名、字符串名表示的是数据块或者代码的首地址。

#include<stdio.h>
#include<stdio.h>

int main()
{
	int a = 100;
	char str[20] = "you are best";
	printf("%#X\n%#X\n",&a,str);
	return 0;
}

9.2指针变量的定义和使用

数据在内存中的地址称为指针,如果一个变量存储了一份数据的指针,我们就称他为指针变量。
指针变量指向数据的地址,数据的形式可以是数组、字符串、函数、变量、指针变量。
定义指针变量必须带*
给指针变量赋值时不能带*

通过指针变量取得数据
指针变量与不同变量
指针变量与取地址

指针是间接用的!

指针变量的定义与初始化:
int *p
int *p=&a;定义指针变量的同时并初始化
int p;定义指针变量
p=&a;给P赋值的时候,不用带
,因为已经知道它是一个指针变量。

定义指针变量时必须带* 给指针变量赋值时不再带*

形象的了解指针:
在这里插入图片描述

通过指针变量获得数据:
给指针变量本身赋值的时候不能加*
int *p=&a;

int *p;
p=&a;
*p=100;

指针是指针
变量是变量
指针变量是指针变量

指针变量代表指向一块内存空间变量

指针指向的是地址 加上*取的是指针的数据!

指针指向的是地址
int a,b,c;
int *p=&a;
p=&a;
*p=100;
c=*p;

9.3C语言指针变量的运算

指针变量的运算与数据类型
数据类型对应的字节
char short int float long double
指针指向的是地址

数组中的所有元素在内存中是连续排列的,如果一个指针指向了数组中的某个元素,那么加 1 就表示指向下一个元素,减 1
就表示指向上一个元素,这样指针的加减运算就具有了现实的意义

指针变量指向的是地址,对地址的操作仅限于加、减、比较;
对地址进行乘除没有意义。

9.4数组指针(指向数组的指针)

字符串指针(指向字符串的指针)
数组指针与指针变量的应用

数组:存放相同类型的数据
结构体:不同数据类型

定义数组的时候,要包含数组名和长度,数组名相当于函数的首地址,因为数组名指向数组的第0个元素,我们第0个元素的地址称为数组的首地址。

指针-地址-数组名

数组指针的定义:如果一个指针指向了数组,就称为数组指针。
数组指针指向的是数组的一个具体元素,而不是整个数组,所以数组指针的类型和数组元素的类型有关。

数组名只能指向数组的开头。
数组指针可以指向数组的任意位置,利用相对位置找数组其他元素位置。

自增与自减和数组指针的应用:

在这里插入图片描述

9.5字符串指针

C语言没有特殊的字符串类型,通常将字符串放在一个字符串数组中。
或者使用一个指针指向字符串。

int *
char *
int
char

疑问?

字符数组全局数据区或栈区的字符串(str):读取和写入
字符串常量常量区的字符串(str[]):读取(不能写入)

9.7指针变量作为函数参数

#include<stdio.h>

int max(int *intarr, int len)
{
	int maxvalue = intarr[0];
	for (int i = 1 ; i < len; i++)
	{
		if (intarr[i]>maxvalue)
		{
			maxvalue = intarr[i];
		}
	}
	return maxvalue;
}

int main() {
	int nums[6];
	int len = sizeof(nums) / sizeof(int);

	for (int i = 0; i < len; i++)
	{
		scanf_s("%d",nums+i);
	}
	printf("%d\n", max(nums, len));
	return 0;
}

9.8C语言指针作为函数返回值

指针变量:指针变量就是*p,本质变量,而不是常量
指针是p,指针指向的是地址p=&a
*p代表一个变量
*p=20;
*p=q;
指针函数的定义:函数的返回值是一个指针。

用指针作为函数的返回值时,因为函数运行结束后会销毁在他内部定义的所有局部数据,包括局部变量、局部数组、形式参数,函数返回的指针不要指向指向这些局部数据,这些局部数据在后续使用过程中可能会引发运行错误。

前面我们说函数运行结束后会销毁所有的局部数据,这个观点并没错,大部分C语言教材也都强调了这一点。但是,这里所谓的销毁并不是将局部数据所占用的内存全部抹掉,而是程序放弃对它的使用权限,弃之不理,后面的代码可以随意使用这块内存。对于上面的两个例子,func() 运行结束后 n 的内存依然保持原样,值还是 100,如果使用及时也能够得到正确的数据,如果有其它函数被调用就会覆盖这块内存,得到的数据就失去了意义。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int *func()
{
	int n = 100;
	return &n;
}
int main()
{
	int *p = func();
	int n;
	//printf("hahaha\n");
	n = *p;
	printf("value=%d\n",n);
	return 0;
}

9.9指向指针的指针(二级指针)

指针也是一种变量,也会占用内存空间,也可以使用&获取他的地址。
每增加以及指针就多一个*号。
int a=100;
int *p1=&a;
int **p2=&p1;
int ***p3=&p2;

9.10空指针NULL以及void指针

gets()不会让用户输入字符串,也不会向指针指向的内存中写入数据。
printf()不会读取指针指向的内容,只是简单的给出提示,让程序员意识到使用了一个空指针。

注意 NULL 和 NUL 的区别:NULL 表示空指针,是一个宏定义,可以在代码中直接使用。而 NUL 表示字符串的结束标志 ‘\0’,它是ASCII码表中的第 0 个字符。NUL 没有在C语言中定义,仅仅是对 ‘\0’ 的称呼,不能在代码中直接使用。

void *的含义:用在函数定义的时候表示函数没有返回值或者没有形式参数。
void *的含义:表示指针指向数据的类型是未知的。

C语言动态内存分配函数malloc的返回值就是void *类型化,在使用时要进行强制类型转换。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main()
{
	char *str = (char *)malloc(sizeof(char)*30);
	gets(str);
	printf("str=%s\n",str);
	return 0;
}

C语言calloc函数内存分配(不能设置分配内存为0)

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main() {
	int a[] = {1,3,5,7,9};
	int *p = a;
	int len_a, len_p;
	len_a = sizeof(a) / sizeof(int);
	len_p = sizeof(p) / sizeof(int);
	printf("len_a is%d\nlen_p is%d\n",len_a,len_p);
	return 0;
}

数组是数组
指针是指针
数组和指针是不等价的。
但是数值名代表数组的首地址。

指针类型:
char *
int *
float *

void *与强制类型转换与内存分配!

数组是一系列数据的集合,没有开始和结束标志,p 仅仅是一个指向 int 类型的指针,编译器不知道它指向的是一个整数还是一堆整数,对 p 使用 sizeof 求得的是指针变量本身的长度。也就是说,编译器并没有把 p 和数组关联起来,p 仅仅是一个指针变量,不管它指向哪里,sizeof 求得的永远是它本身所占用的字节数。

数组其实也有类型:

对于数组 a,它的类型是int [6],表示这是一个拥有 6 个 int 数据的集合,1 个 int 的长度为 4,6 个 int 的长度为 4×6 = 24,sizeof 很容易求得。

对于指针变量 p,它的类型是int *,在 32 位环境下长度为 4,在 64 位环境下长度为 8。

归根结底,a 和 p 这两个符号的类型不同,指代的数据也不同,它们不是一码事,sizeof 是根据符号类型来求长度的,a 和 p 的类型不同,求得的长度自然也不一样。

对于二维数组,也是类似的道理,例如int a[3][3]={1, 2, 3, 4, 5, 6, 7, 8, 9};,它的类型是int [3][3],长度是 4×3×3 = 36

程序语言-人类语言
整数、小数、指针、数组等不同类型的数据都是对内存的抽象,他们的名字用来指代不同的内存块,程序员在编码过程中不需要直接面对内存。

C语言标准规定,当数组名作为数组定义的标识符,也就是定义和声明数组时、sizeof操作时、&操作时,他才表示整个数组本身,在其他的表达式中,数组名会被转换成指向第0个元素的指针。

对数组下标的引用总是可以写成一个指向数组起始地址的指针加上偏移量。
a[i]=*(a+i)

取下标操作符是建立在指针的基础上,他的作用是使一个指针和一个整数相加,产生一个新的指南,然后在这个新的指针上取得数据。

数组存的是数据,数据就有不同的类型、不同的类型就有不同的长度。

数组如何作为函数参数:

关于数组和指针可交换性的总结:
a[i]转换成*(a+i)
指针作为函数参数传递时,可以用下标形式访问指针。
作为函数形参的数组始终会被编译器修改成指向第一个元素的指针。
9.13指针数组
指针变量-变量
数组指针:指向数组的指针
字符串指针:指向字符串的指针

指针数组:由指针构成的数组,也即数组中的每个元素都是指针。
int *a[6];
int *(a[6])

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main() {
	int a = 3, b = 6, c = 9;
	//定义一个指针数组
	int *arr[] = { &a,&b,&c };
	//定义一个指向指针数组的指针
	int *(*parr) = arr;
	/*
	arr 是一个指针数组,它包含了 3 个元素,每个元素都是一个指针,在定义 arr 的同时,我们使用变量 a、b、c 的地址对它进行了初始化,这和普通数组是多么地类似。

parr 是指向数组 arr 的指针,确切地说是指向 arr 第 0 个元素的指针,它的定义形式应该理解为int *(*parr),括号中的*表示 parr 是一个指针,括号外面的int *表示 parr 指向的数据的类型。arr 第 0 个元素的类型为 int *,所以在定义 parr 时要加两个 *。

第一个 printf() 语句中,arr[i] 表示获取第 i 个元素的值,该元素是一个指针,还需要在前面增加一个 * 才能取得它指向的数据,也即 *arr[i] 的形式。

第二个 printf() 语句中,parr+i 表示第 i 个元素的地址,*(parr+i) 表示获取第 i 个元素的值(该元素是一个指针),**(parr+i) 表示获取第 i 个元素指向的数据。
	*/
	int len = sizeof(arr) / sizeof(int);
	for (int i = 0; i <len ; i++)
	{
		printf("%d", *arr[i]);
		printf("%d\n",**(parr+i));

	}
	return 0;
}

指针数组和字符串相结合:
指针数组:数组的每个元素都是指针
字符串指针:指向字符串的子帧
数组指针:指向数组的指针

字符数组 str 中存放的是字符串的首地址,不是字符串本身,字符串本身位于其他的内存区域,和字符数组是分开的

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main() {
	char *str0 = "you";
	char *str1 = "like";
	char *str2 = "heytea";
	char *str[] = { str0,str1,str2 };
	printf("%s\n%s\n%s\n",str[0],str[1],str[2]);
	return 0;
}

指针数组和二级指针:
指向指针的指针
指针指向的是地址
指针变量也是变量也有地址

9.15二维数组指针

指针和二维数组的关系:
二维数组指针的表示:
int (*p)[4]=a、
*表明p是一个指针
该指针指向一个数组
数组的类型为int[4]

数组名a在表达式会被转换成和p等价的指针。
在定义时或者和sizeof、&一起使用时才表示整个数组,出现在表达式中就会被转换成指向数组第0个元素的指针。

使用指针遍历二维数组:

指针数组:
二维数组指针:

对指针进行加法以及减法运算时,它前进后退的步长与他指向的数据类型有关。

数组名a在表达式中也会被转换成和p等价的指针。

*(p+1)单独使用时表示的是第 1 行数据,放在表达式中会被转换为第 1 行数据的首地址,也就是第 1 行第 0 个元素的地址,因为使用整行数据没有实际的含义,编译器遇到这种情况都会转换为指向该行第 0 个元素的指针;就像一维数组的名字,在定义时或者和 sizeof、& 一起使用时才表示整个数组,出现在表达式中就会被转换为指向数组第 0 个元素的指针。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main() {
	int a[][4] = { 0,1,2,3,4,5,6,7,8,9,10,11 };

	int(*p)[4];
	p = a;
	printf("%d\n",*(*(p+1)+1));
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			printf("%d\n",*(*(a+i)+j));
		}
	}
	return 0;
}

指针数组:指针组成的数组,数组的每个元素都是指针。
二维数组指针:指针第一次访问整行,然后访问整列。

9.16函数指针

函数指针,顾名思义,就是一个指针,只不过这个指针指向的是一个函数。

用指针实现对函数的调用:

#include<stdio.h>

int max(int a, int b)
{
	return a > b ? a : b;
}
int main() {
	int x, y, maxvalue;
	int(*pmax)(int a, int b);
	pmax = max;
	printf("input two numbers\n");
	scanf_s("%d%d",&x,&y);
	maxvalue = (*pmax)(x,y);
	printf("%d\n",maxvalue);
	return 0;
}

9.17牢记复杂指针操作

为了能够通过指针来遍历数组,在定义数组指针的时候需要降维处理,二维数组指针指向的是一维数组;
在表达式中,数组名会进行降维处理,从整个数组变为首元素的地址。

运算符的优先级
函数指针:指针指向的是一个函数

9.18 main()函数的高级用法:接收用户输入的数据

main()函数是C语言的入口函数,有且只有一个。
int main()
int main(int argc,char *argv[])

9.19C语言指针的总结

指针就是内存的地址

指针、地址
变量、指针变量
基本数据类型
数组、函数
指向指针的指针(二级指针)
地址助记符

在编码过程中,我们认为变量名表示的是数据本身,而函数名、字符串名、数组名表示的代码块或者数据块的首地址(、表、达式)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值