首页 > 其他分享 >C语言:指针(1)

C语言:指针(1)

时间:2024-07-29 23:27:36浏览次数:15  
标签:const 变量 int C语言 pa printf 指针

一. 内存和地址

比如,我们的内存就相当⼀栋宿舍楼,楼里有很多的房间,每个房间都有一个房间号,每个房间里都住着8个人。这时如果你的朋友想要来找你,我们只需要把房间号告诉他就能快速的找到我们。

然而,,在计算机中每个房间就相当一个内存单元,每个内存单元都有一个编号,每个编号对应着一个房间号,房间里的8个人对应着8个比特位。

  1. 1Byte = 8bit
  2. 1KB = 1024Byte
  3. 1MB = 1024KB
  4. 1GB = 1024MB
  5. 1TB = 1024GB
  6. 1PB = 1024TB 

然而,在生活中我们把房间号叫地址,在计算机中我们把内存单元的编号也称为地址。在C语言中我们给地址起了新的名字叫:指针

所以我们可以理解为: 内存单元的编号 == 地址 == 指针

二. 指针变量和地址

1.取地址操作符(&)

在C语言中,如果我们想要的到地址就需要用到取地址操作符(&)

#include<stdio.h>
int main()
{
	int a = 10;
	printf("%p\n", &a);
	return 0;
}

在x86环境下 

2. 指针变量和解引用操作符(*) 

这时,我们已经将a的地址取出来了,我们肯定要将地址存储起来,方便以后的使用,那么我们该如何存储地址呢?这就需要用到指针变量了。

1.指针变量

#include<stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;
	return 0;
}

2.指针类型

  • int* pa = &a;
  • *是指pa为指针变量
  • int是指pa指向的对象为int类型

3.解引用操作符 (*)

既然,我们已经将地址存储起来了,那么我们该如何使用他呢?这时就要用到解引用操作符(*)了。

#include<stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;
	printf("%d\n", *pa);
	return 0;
}

还是,这段代码如果我们想用指针变量打印a的值我们就可以用*pa来解决。 

#include<stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;
	*pa = 20;
	printf("%d\n", a);
	return 0;
}

同时,如果我们想要改变a的值,我们只需要给*p重新赋值即可。

3.指针变量的大小

 我们可以通过代码来了解一下指针变量的大小。

#include<stdio.h>
int main()
{
	printf("%zd\n", sizeof(int*));
	printf("%zd\n", sizeof(char*));
	printf("%zd\n", sizeof(float*));
	printf("%zd\n", sizeof(double*));
	printf("%zd\n", sizeof(long*));
	printf("%zd\n", sizeof(short*));
	printf("%zd\n", sizeof(long long*));
	return 0;
}

在x86环境下

在x64环境下

我们可以看到在x86环境下所有类型指针变量的大小都是4个字节,x64下为8个字节。所以我们可以得出结论。

  • 32位平台下地址是32个bit位,指针变量大小是4个字节(x86)
  • 64位平台下地址是64个bit位,指针变量大小是8个字节   (x64)
  • 注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的

那么在相同平台下,大小都相同,那指针变量类型有什么意义呢?

 三.指针变量类型的意义

1.指针的解引用

#include<stdio.h>
int main()
{
	int a = 0x11223344;
	int* pa = &a;
	*pa = 0;
	return 0;
}

在int*类型下,将*pa赋值为0,会将四个值都赋为0

 

在char*类型下,将*pc赋值为0,只会将第一个赋值为0

结论:指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字节)。

 下面这些时不同的类型,在不同环境下的字节大小

2.指针+-整数

#include <stdio.h>
int main()
{
	int n = 10;
	int* pi = &n;
	char* pc =&n;

	printf("&n   =%p\n", &n);
	printf("*pi  =%p\n", pi);
	printf("*pi+1=%p\n", pi + 1);
	 
	printf("*pc  =%p\n", pc);
	printf("*pc+1=%p\n", pc + 1);
	return 0;
}

 我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节

指针+1,其实跳过1个指针指向的元素。 

3.void* 指针

在指针类型中有⼀种特殊的类型是 void * 类型的,可以理解为无具体类型的指针或者叫泛型指 针),这种类型的指针可以用来接受任意类型地址。但是也有局限性,void* 类型的指针不能直接进行指针的+-整数和解引用的运算。

 例如,如果我们用void* pa存放a的地址,我们发现如果我们要改变a的值,是无法改变的,因为我们并不知道要存放几个字节,同时我们也无法进行指针的运算,因为我们也不知道要跳过几个字节。

四.const 

1.const修饰变量

const的作用其实是使变量不能被修改。

我们可以看下面这段代码。

我们可以发现当我们想改变a的值时,我们发现a不许需被修改了,这就是const的作用。

const既然能修饰变量,那么是不是也能修饰指针变量呢,答案是可以的。

2.const修饰指针变量

一般来讲const修饰指针变量,有两种形式,可以放在*的左边,也可以放在*的右边,但两者之间的意义是不⼀样的。

1.const 放在*的左边做修饰

形式:

const int * pa

或者

int const * pa

如果我们想要改变*pa 和pa的值。 

我们可以看到当我们想要改变* pa的值时,我们是不被允许的,但是改变pa的值是被允许的。

2.const 放在*的右边做修饰

形式

int * const pa 

 如果我们想要改变*pa 和pa的值。 

我们会发现这时*pa被允许,pa不被允许了。

其实const还有第三种修饰方式,放在左右两边。

3.const 放在左右两边做修饰

形式:

const int * const pa

这时,如果我们想要改变*pa 和pa的值。

我们发现*pa和pa都不被允许改变了。

4.结论

  • 结论:const修饰指针变量的时候
  • const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本身的内容可变。
  • const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指 向的内容,可以通过指针改变。
  • const如果放在左右两边,修饰的是指针指向的内容和指针变量本身,保证了指针指向的内容和指针变量的内容不能被修改。

五.指针运算

指针的基本运算有三种:

  1. 指针+- 整数
  2. 指针-指针
  3. 指针的关系运算

1.指针+- 整数

我们可以举个例子

#include <stdio.h>
int main()
{
	
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr[0];
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));//p+i 这⾥就是指针+整数
	}
	return 0;
}

2. 指针-指针

我们将首地址传给指针变量p,如果指针解引用的值不等于/0,就让p++,直到找到\0的地址,指针就会停在\0的地址处,让p-s(起始地址)就是元素的个数

//指针-指针
#include <stdio.h>
int my_strlen(char* s)
{
	char* p = s;
	while (*p != '\0')
		p++;
	return p - s;
}
int main()
{
	printf("%d\n", my_strlen("abc"));
	return 0;
}

3.指针的关系运算

将数组的首地址赋给p,让p<arr+sz,每循环一次p++,让p往下走,打印数组里的值。

//指针的关系运算
#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr[0];
	int sz = sizeof(arr) / sizeof(arr[0]);
	while (p < arr + sz) //指针的⼤⼩⽐较
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

六.野指针

野指针的形成

1.指针未初始化

#include <stdio.h>
int main()
{
	int* p;//局部变量指针未初始化,默认为随机值
	*p = 20;
	return 0;
}

2.指针越界访问

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p = &arr[0];
	int i = 0;
	for (i = 0; i <= 11; i++)
	{
		//当指针指向的范围超出数组arr的范围时,p就是野指针
		*(p++) = i;
	}
	return 0;
}

3.指针指向的空间释放

在这段代码中,test()函数中,将&n返回后,虽然我们将地址成功返回,但是再返回后n所申请的内存空间会被销毁,空间的到释放后,我们虽然能根据地址找到但是没有访问权限。

#include <stdio.h>
int* test()
{
	int n = 100;
	return &n;
}
int main()
{
	int* p = test();
	printf("%d\n", *p);
	return 0;
}

七.传值调用和传址调用

1.传值调用

这段代码就是经典的传值调用

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int a, b;
	scanf("%d %d", &a, &b);
	int r = Add(a, b);
	printf("%d\n", r);
}

 

然而我们看看下一段代码

#include <stdio.h>
void Swap1(int x, int y)
{
	int tmp = x;
	x = y;
	y = tmp;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d b=%d\n", a, b);
	Swap1(a, b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

 我们发现a和b的值并没有发生交换。这时我们可以试试另外一种方法。

2.传址调用

#include <stdio.h>
void Swap2(int* px, int* py)
{
	int tmp = 0;
	tmp = *px;
	*px = *py;
	*py = tmp;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d b=%d\n", a, b);
	Swap2(&a, &b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

 

我们发现这时我们成功交换了a和b的值。那为什么第一种方法不行呢?

这是因为实参传递给形参的时候,形参会单独创建一份临时空间来接收实参,对形参的修改不影响实参

 第二种方法,我们是直接将地址传过去,让他们直接调用地址,通过解引用来改变值,从而达到交换的目的。

好了今天的分享就到这里吧,我们下一篇见。

标签:const,变量,int,C语言,pa,printf,指针
From: https://blog.csdn.net/zm3rttqs9f/article/details/140780909

相关文章

  • C语言学习笔记 Day6(程序运行结构)
    Day6 内容梳理:1、Chapter4 程序运行结构:4.0概述,4.1条件判断语句(if/switch),4.2循环语句(while/for)Chapter4 程序运行结构4.0概述基本的3种程序运行结构:    ①顺序结构:程序按顺序执行,不发生跳转    ②选择结构:按是否满足条件,执行相对应的......
  • 嵌入式学习之路 6(C语言基础学习——循环控制)
    目录一、构成循环的要素二、循环语句1、while(表达式)2、do-while3、for循环4、break和continue一、构成循环的要素1、在C语言中,构成循环的要素主要包括以下几个方面:1. 循环控制变量:用于控制循环的执行次数和条件。它通常在循环开始前进行初始化,并在每次循环迭代中进......
  • 嵌入式学习之路 7(C语言基础学习——数组)
        数组是一组相同类型数据的集合,也是一组相同类型变量的集合,同时数组本身也是一种数据类型。    在需要定义多个相同类型的产量时,按照以往的方法一个一个定义就相当繁琐,而数组可以批量处理多个数据。一、一维数组1、数组语法:类型说明符 数组名 [常量......
  • 【c语言】do while、for循环
    1.dowhile语义:先执行语句,再判断表达式;真,继续执行语句,假,结束。do{语句;}while(表达式);//一定记得加分号;流程图:while与dowhile区别1.while先判断后执行2.dowhile先执行后判断(循环体至少执行一次)2、for循环:for(表达式1;表达式2,表达式3)//{循环体语......
  • 【c语言】数组相关知识
    概念:数组是一组相同类型元素的集合(一种相同类型的变量的集合)      数组也是一种数据类型(构造类型),可以批量处理多个数据。      数组有一维数组、二维数组;特点:连续性      有序性      单一性数组在内存中开辟是线性连续且......
  • 【C语言】输入、输出函数知识、getchar()、putchar()、 scanf()、printf()
    函数的声明和定义1.1 函数声明1.告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。2.函数的声明一般出现在函数的使用之前。要满足先声明后使用。3.函数的声明一般要放在头文件中的。1.2C本身是不提供输入输出功能的,需要......
  • 线程参数传递 同一个结构体指针,并且要传递id(0-xx)的方法
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、代码示例?总结前言提示:这里可以添加本文要记录的大概内容:线程参数传递同一个结构体指针,并且要传递id(0-xx)的方法在创建线程时候可能会遇到不仅要传递全局唯一的结构体指针给线程,还......
  • 随机数函数 和 猜数字游戏(c语言初学者拔高)
    目录1.随机数的生成方法1.1rand()函数1.1.1函数原型1.1.2函数功能1.2srand()函数1.2.1函数原型1.2.2函数功能1.3time()函数1.2.1函数原型1.1.2函数功能1.4设置随机数的范围2.猜数字游戏2.1普通版:结构逻辑解析2.1.1程序代码2.1.2 细节答疑2.2拓......
  • C语言------指针
    一、指针的理解与定义1.1变量的访问方式计算面中程序的运行都是在内存中进行的,变量也是内存中分配的空间,且不同类型的变量占用不同大小的空间。那如何问内存中变量存储的数据呢?有两种方式:直接访问和间接访问。直接访问:直接使用变量名进行的访问,以前的程序都是采用这种方式。......
  • c语言字符数组
            字符数组与字符串,字符数据输出用%s表示              上面两种方式的区别:             ......