C语言指针之必须要掌握的指针基础知识

目录
  • 一、指针概述
    • 指针的创建
    • 指针的大小
    • 如何使用指针
    • 二级指针
  • 二、野指针
    • 形成野指针的原因
    • 如何规避野指针
  • 三、指针的基本运算
    • 指针± 整数
    • 指针-指针
  • 四、指针和数组
  • 五、指针数组
  • 总结

一、指针概述

指针是个变量,存放内存单元的地址(编号)。

指针的创建

在定义指针变量的时候,在变量前面加上' * ',代表这个变量是一个指针,再往前面加上一个类型名,就代表指针的类型,称为XX指针。

指针的初始化:

使用&(取地址操作符)可以获得变量的地址,将其赋值给已经定义好的指针变量,需要它们的类型相同,类型不同的时候可以使用强制转换。如果暂时不知道需要存放什么地址的时候,可以先让它指向NULL((void*)0),NULL本质就是0,0这个地址是不允许存储的。

#include <stdio.h>
int main()
{
	int a = 10;//在内存中开辟一块空间
	int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
				//将a的地址存放在p变量中,p就是一个指针变量。
	return 0;
}

指针的大小

指针可以是不同的类型,char*、int*、float*、double*、long*等我们熟知的类型,那指针在内存中占多大的空间呢?

对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的是产生一个电信号正电/负电(1或者0)
那么32根地址线产生的地址就会是:

00000000 00000000 00000000 00000000
……
111111111 111111111 111111111 111111111
共有232个地址,每一个地址能指向一个内存单元(一字节),那么有232个内存单元。

我们知道一个内存单元的大小为一字节,一根地址线是32位01组成的,那么要存放指针(地址),也就需要32个bit,也即4字节。所以在32位平台下不论是什么类型的指针都是4个字节。

同理,在64位平台下有64根地址线,每个地址是64位,所以需要8个字节。

如何使用指针

使用' * '操作符对指针进行解引用,可以获取到指针指向空间的值

我们来运行下方代码。

#include <stdio.h>
int main()
{
	int n = 0x11223344;
	char* pc = (char*)&n;//将n的地址(指针)强制转换成char* 类型
	int* pi = &n;
	*pc = 0;//改变它指向空间的值
	*pi = 0;
	return 0;
}

我们开始调试代码,查看n的内存,最左边是地址,右边是以16进制显示内存里存放的数据,而n也是以16进制存进去的,两个16进制代表一个字节,如下图:

当执行完语句 *pc=0;

因为pc指向的是char* 类型,只能访问一个字节,所以只能修改一个字节。

再执行*pi = 0;

此时n的内存数据全部变成了0,说明int*类型的指针可以访问4个字节。

小结:

指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个节)。 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? 这就是 二级指针 。
即存放指针变量的地址的指针,二级指针指向的空间的值是一个一级指针。

int main()
{
	int a = 20;
	int* p = &a;
	int** pp = &p;
	printf("%d\n", *p);//解引用即可拿到指针指向空间里面的值
	printf("%d\n", *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;
	int i = 0;
    for(i=0; i<=11; i++)
	{
  		//当指针指向的范围超出数组arr的范围时,p就是野指针
    	*(p++) = i;
	}
	return 0;
}

3.指针指向的空间释放

当动态开辟的内存被释放后,此时的指针就属于野指针,它指向的位置它不能正常访问。关于动态内存分配,后续会细讲。

int main()
{
	//开辟一个整形空间
	int* p = (int*)malloc(sizeof(int));
	//释放该空间
	free(p);
	//此时p即为野指针,因为它指向的空间已经无法访问,一般需要将其置空
	//将其指向空指针,防止后续调用出错
	p = NULL;
	return 0;
}

如何规避野指针

  • 指针初始化
  • 小心指针越界
  • 指针指向空间释放即使置NULL
  • 指针使用之前检查有效性

三、指针的基本运算

关于指针的运算一般有两个,指针+指针,语义是合法,但没有意义。

  • 指针± 整数
  • 指针-指针

指针± 整数

对int和char类型的指针分别进行+1操作,%p以16进制打印

int main()
{
	int n = 10;
	char* pc = (char*)&n;//将n的地址(指针)强制转换成char* 类型
	int* pi = &n;
	printf("&n:    %p\n", &n);
	printf("pc:    %p\n", pc);
	printf("pc + 1:%p\n", pc + 1);
	printf("pi:    %p\n", pi);
	printf("pi + 1:%p\n", pi + 1);
	return 0;
}

小结:

指针的类型决定了指针向前或者向后走一步有多大(距离)。

指针-指针

int main()
{
	int n = 10;
	char* pc = (char*)&n;//将n的地址(指针)强制转换成char* 类型
	int* pi = &n;
	printf("&n:    %p\n", &n);
	printf("pc:    %p\n", pc);
	printf("pc + 1:%p\n", pc + 1);
	printf("pi:    %p\n", pi);
	printf("pi + 1:%p\n", pi + 1);
	return 0;
}

小结:

指针-指针等于它们之间相差的类型数据的个数。即本例的第10个元素和第1个元素之间相差9。

四、指针和数组

一起来看一下数组名是什么

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	return 0;
}

可见数组名和数组首元素的地址是一样的。

结论: 数组名表示的是数组首元素的地址。也是一个指针

那么我们就可以用一个指针来代替数组名,如下代码:

int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
	//对数组赋值
	for (i = 0; i < sz; i++)
	{
		*(p + i) = i;
	}
	//打印数组数据
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

注意:

数组名在下面两种情况下不是首元素的地址

1.sizeof(数组名) - 这里的数组名不是首元素的地址,是表示整个数组的,这里计算的是整个数组的大小,单位还是字节

2.&数组名 - 这里的数组名不是首元素的地址,是表示整个数组的,拿到的是整个数组的地址

int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr);
	printf("sizeof(arr)计算的是整个数组的大小:%d\n", sz);
	printf("数组首地址:      %p\n", arr);
	printf("数组首元素地址:  %p\n", &arr[0]);
	printf("数组的地址:      %p\n", &arr);
	printf("数组首地址+1:    %p\n", arr + 1);
	printf("数组首元素地址+1:%p\n", &arr[0] + 1);
	printf("数组的地址+1:    %p\n", &arr + 1);
	//数组名确实是首元素的地址
	//但是有2个例外:
	//1. sizeof(数组名)  - 这里的数组名不是首元素的地址,是表示整个数组的,这里计算的是整个数组的大小,单位还是字节
	//2. &数组名 - 这里的数组名不是首元素的地址,是表示整个数组的,拿到的是整个数组的地址
	//
	return 0;
}

五、指针数组

存放指针的数组。

int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int* arr[10] = { &a,&b,&c };
	for (int i = 0; i < 3 ; i++)
	{
		printf("%d ", *(arr[i]));
	}
	return 0;
}

总结

指针是C语言非常重要的一部分,内容繁多不易懂,本文仅介绍了一些基本知识,后续还会深入了解指针。

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • C语言进阶学习之指针

    目录 1.指针概念回顾 2.字符指针 3.数组指针和指针数组 3.1数组指针的含义 3.2&数组名vs数组名 3.3数组指针 4.数组传参和指针传参 4.1一维数组传参 4.2二维数组传参 4.3一级指针传参 4.4二级指针传参 5.函数指针 6.函数指针数组 7.指向函数指针数组的指针 8.回调函数 总结 1.指针概念回顾 指针的基本概念: 指针是一个变量,用来存放地址,地址唯一标识一块内存空间.指针的大小是固定的4/8个字节(32位平台/64位平台).指针是有类型,指针的类型决定了指针的±整

  • C语言中的指针 初阶

    目录 1.指针是什么 2.指针和指针类型 3.野指针 3.1野指针成因 3.2如何规避野指针 4.指针的运算 4.1指针±整数 4.2指针-指针 4.3指针的关系运算 5.指针和数组 6.二级指针 7.指针数组 1.指针是什么 初学者都有一个疑问,那就是指针是什么?简单的说,就是通过它能找到以它为地址的内存单元. 地址指向了一个确定的内存空间,所以地址形象的被称为指针. int main() { int a = 10; int* pa = &a; return 0; } //pa是用来存放地址(

  • C语言经典指针笔试题详解

    目录 题目一(有关传值调用与非法访问) 题目二 (返回栈空间地址的问题 ) 题目三 (区别传值调用的传址调用) 题目四 (free释放的时机)

  • C语言中的初阶指针详解

    目录 1.指针是什么 2.指针和指针类型 3.野指针 3.1野指针成因 3.2如何规避野指针 4.指针的运算 4.1指针±整数 4.2指针-指针 4.3指针的关系运算 5.指针和数组 6.二级指针 7.指针数组 ​ 总结 1.指针是什么 ​ 初学者都有一个疑问,那就是指针是什么?简单的说,就是通过它能找到以它为地址的内存单元. 地址指向了一个确定的内存空间,所以地址形象的被称为指针. int main() { int a = 10; int* pa = &a; return 0; } //pa是

  • C语言中的指针新手初阶指南

    目录 1.指针是什么 2.指针和指针类型 3.野指针 3.1野指针成因 3.2如何规避野指针 4.指针的运算 4.1指针±整数 4.2指针-指针 4.3指针的关系运算 5.指针和数组 6.二级指针 7.指针数组 总结 1.指针是什么 ​ 初学者都有一个疑问,那就是指针是什么?简单的说,就是通过它能找到以它为地址的内存单元. 地址指向了一个确定的内存空间,所以地址形象的被称为指针. int main() { int a = 10; int* pa = &a; return 0; } //pa是用来

  • C语言运用函数指针数组实现计算器功能

    本文实例为大家分享了C语言运用函数指针数组制作计算器的具体代码,供大家参考,具体内容如下 先来回顾一下概念: 指针数组 -- 存放指针的数组 函数指针 -- 存放函数地址的指针 函数指针数组 -- 存放函数指针的数组 接下来说说这次要制作的计算器的功能: 1.add -- 加法 2.sub -- 减法 3.mul -- 乘法 4.div -- 除法 0.exit -- 退出 具体来通过代码讲解: (1)首先写一个菜单程序,在运行程序时首先打印一次菜单. void menu() { printf(

  • 详解C语言中的函数、数组与指针

    1.函数:当程序很小的时候,我们可以使用一个main函数就能搞定,但当程序变大的时候,就超出了人的大脑承受范围,逻辑不清了,这时候就需要把一个大程序分成许多小的模块来组织,于是就出现了函数概念:   函数是C语言代码的基本组成部分,它是一个小的模块,整个程序由很多个功能独立的模块(函数)组成.这就是程序设计的基本分化方法: (1) 写一个函数的关键: 函数定义:函数的定义是这个函数的实现,函数定义中包含了函数体,函数体中的代码段决定了这个函数的功能: 函数声明:函数声明也称函数原型声明,函数的原

  • C语言指针基础知识实例讲解

    对程序进行编译的时候,系统会把变量分配在内存单位中,根据不同的变量类型,分配不同的字节大小.比如int整型变量分配4个字节,char字符型变量分配1个字节等等.被分配在内存的变量,可以通过地址去找到,内存区每一个字节都有一个编号,地址也可以形象的理解成我们生活中的住址,通过住址找到每一个人所在的地方.指针作为一个变量用来存放地址,可以通过指针来改动变量. 上图就是一个简单的定义一个一级指针变量和利用指针改变变量数值的过程.int*表示整型指针,*p表示解引用操作,就是利用指针找到a的地址然后再改

  • C语言指针之必须要掌握的指针基础知识

    目录 一.指针概述 指针的创建 指针的大小 如何使用指针 二级指针 二.野指针 形成野指针的原因 如何规避野指针 三.指针的基本运算 指针± 整数 指针-指针 四.指针和数组 五.指针数组 总结 一.指针概述 指针是个变量,存放内存单元的地址(编号). 指针的创建 在定义指针变量的时候,在变量前面加上' * ',代表这个变量是一个指针,再往前面加上一个类型名,就代表指针的类型,称为XX指针. 指针的初始化: 使用&(取地址操作符)可以获得变量的地址,将其赋值给已经定义好的指针变量,需要它们的类型

  • C语言超详细讲解指向函数的指针

    目录 一.函数的指针 二.指向函数的指针变量 三.调用函数的两种方式 四.指向函数的指针的作用 五.用指向函数的指针作函数参数(重点) 六.为什么要将指向函数的指针变量作为函数的形参(重点) 一.函数的指针 首先,函数名代表函数的起始地址,调用函数时,程序会从函数名获取到函数起始地址,并从该地址起执行函数中的代码,函数名就是函数的指针,所以我们可以定义一个指向函数的指针变量,用来存放函数的起始地址,这样一来,就可以通过该变量来调用其所指向的函数. 二.指向函数的指针变量 定义指向函数的指针变量

  • C语言容易被忽视的函数设计原则基础

    目录 一.函数设计原则 二.总结 一.函数设计原则 函数从意义上应该是一个独立的功能模块 函数名要在一定程度上反映函数的功能 函数参数名要能够体现参数的意义 尽量避免在函数中使用全局变量 当函数参数不应该在函数体内部被修改时,应加上 const 声明 如果参数是指针,且仅作输入参数,则应加上 const 声明,如下: 不能省略返回值的类型 如果函数没有返回值,那么应声明为 void 类型对参数进行有效性检查 对于指针参数的检查尤为重要 不要返回指向“栈内存”的指针 栈内存在函数体结束时被自动释放

  • C语言基础知识分享续篇

    目录 写在前面 数组 数组使用 函数 字符串 strlen && sizeof sizeof strlen 转义字符 操作符 选择语句 if else switch 循环语句 for while do while 跳出语句 contine break 指针 自定义类型 struct 写在前面 好了,现在我们开始C语言的第二个部分.今天我们需要看下面几个知识点,都是非常简单的,我们主要认识一下. 数组 我们知道一个一个属性可以用一个类型去表示,那么我想问的是如果是一个属性的多个呢?也就是多个

  • GO语言(golang)基础知识

    今天说一些golang的基础知识,还有你们学习会遇到的问题,先讲解hello word 复制代码 代码如下: package main import "fmt" func main() {    fmt.Println("你好,我们"); } package name 包机制,每一个独立的go程序都需要有一个package main的申明,主要是要为下边入口函数main()做申明的,import和java一样导入包用的 就是下边我们函数用的fmt.Println()

  • IOS开发之路--C语言基础知识

    概览 当前移动开发的趋势已经势不可挡,这个系列希望浅谈一下个人对IOS开发的一些见解,这个IOS系列计划从几个角度去说IOS开发: C语言 OC基础 IOS开发(iphone/ipad) Swift 这么看下去还有大量的内容需要持续补充,但是今天我们从最基础的C语言开始,C语言部分我将分成几个章节去说,今天我们简单看一下C的一些基础知识,更高级的内容我将放到后面的文章中. 今天基础知识分为以下几点内容(注意:循环.条件语句在此不再赘述): Hello World 运行过程 数据类型 运算符 常用

  • 一般函数指针和类的成员函数指针深入解析

    函数指针是通过指向函数的指针间接调用函数.函数指针可以实现对参数类型.参数顺序.返回值都相同的函数进行封装,是多态的一种实现方式.由于类的非静态成员函数中有一个隐形的this指针,因此,类的成员函数的指针和一般函数的指针的表现形式不一样. 1.指向一般函数的指针函数指针的声明中就包括了函数的参数类型.顺序和返回值,只能把相匹配的函数地址赋值给函数指针.为了封装同类型的函数,可以把函数指针作为通用接口函数的参数,并通过函数指针来间接调用所封装的函数.下面是一个指向函数的指针使用的例子. 复制代码

  • c语言 树的基础知识(必看篇)

    第一.树的定义: 1.有且只有一个称为根的节点 2.有若干个互不相交的子树,这些子树本身也是一颗树 第二.专业术语: 树的深度:从根节点到最低层,节点的层数 ,称之为树的深度.  根节点是第一层 结点的层次:根节点为第一层,根节点的子节点为第2层,以此类推 叶子节点:没有子节点的节点 非终端节点:实际就是非叶子节点 结点度: 子节点的个数称为度树的度 第三.树的分类 一般树:任意一个节点的子节点的个数不受限制 二叉树:任意一个节点的子节点最多2个,且子节点的位置不可更改 满二叉树:在不增加层数的

随机推荐