C语言指针详解(由浅入深)

指针基础知识

计算机中所有的数据都必须放在内存中,不同类型的数据占用的字节数不一样,例如
int 占用 4 个字节,char 占用 1 个字节。为了正确地访问这些数据,必须为每个字节都编上
号码,就像门牌号一样,每个字节的编号是唯一的,根据编号可以准确地找到某个字节。
我们将内存中字节的编号称为地址(Address)或指针(Pointer)。地址从 0 开始依次增加
,对于 32 位环境,程序能够使用的内存为 4GB。指针与指针变量最小的地址为 0x0000 0000,最大的地址为 0xFFFF FFFF。

指针理解

一级指针的定义

//基类型* 变量名=地址
int a = 1;
int* pointer = &a

如图所示
在这里插入图片描述

指针变量的值是地址

例如

//声明三个整型
int a,b,c;
//声明三个整型指针变量
int* p1,p2,p3;

指针的使用

//*变量名 = 基类型的值

指针理解实例

inta=10,b=20;
int* p=&a;
int* q=&b;
int**pp=&p;
int***ppp=&pp;

存储原理图
在这里插入图片描述
原理分析
定义两个整型变量ab,他们存储的分别是10 20,在定义的同时,系统给这两个变量分配地址,为0x1,0x2,再定义一个指向整型的指针变量p,在定义的同时,系统给这个变量分配地址,为0x3,也可以说是存储整型变量地址的指针变量p,它存储的是变量a的地址0x1,因此指针p就可以找到a的地址,就可以通过*p来访问变量a。再定义一个指针变量pp,它是指向指针p的指针,我们称其为二级指针,它存储的是一级指针p的地址0x3,以此类推。

指针变量的大小取决于操作系统,x86是4字节,x64是8字节

例题:
封装一个函数,即实现两个数的和,又实现两个数的积
编程实现

void operate1(int* arr, int a, int b) {
	arr[0] = a + b;
	arr[1] = a * b;
}
void operate2(int* sum, int* mul, int a, int b) {
	*sum = a + b;
	*mul = a * b;
}
int main() {
	int arr[2];
	operate1(arr, 2, 3);
	printf("%d %d\n", arr[0], arr[1]);
	int sum, mul;
	operate2(&sum, &mul, 3, 4);
	printf("%d %d \n", sum, mul);
	return 0;
}

测试结果
在这里插入图片描述
operate1内部实现机理
在这里插入图片描述
operate2内部实现机理
在这里插入图片描述

* 含义

总结:* 含义:
1.指针的定义 int*p = &a
2.解引用功能 p = 20;
3.乘法 10
20;

*和&谜题分析

如: int a =10; int *pa = &a;

假设有一个 int 类型的变量 a,pa 是指向它的指针,那么*&a, 和 &pa ,&pa分别是什
么意思呢?
&a可以理解为(&a),&a表示取变量 a 的地址(等价于 pa), *(&a)表示取这个地址上的
数据(等价于 pa),绕来绕去,又回到了原点,&a仍然等价于 a。
&*pa 可以理解为&(*pa),*pa表示取得 pa 指向的数据(等价于 a),&(*pa) 表示数据的
地址(等价于 &a),所以 &*pa等价于 pa

指针+整型 含义

指针+n:指针的偏移量为n*sizeof(内置类型)

int arr[10];
int* p = arr;             
p <=> arr <=> &arr[0];        *(p+0) <=> arr[0];
p+1 <=> &arr[1];              *(p+1) <=> arr[1];
p+3 <=> &arr[3];              *(p+3) <=> arr[3];

[ ] 具有解引用的功能

指针 - 指针 含义:

q - p 之间相差的单元格子数

一级指针和const的结合

const与指针配合使用的作用:
1.限制指针变量
2.限制指针变量指向的数据
3.既要限制指针变量又限制指针变量指向的数据(双重限定)

限制指针变量本身

如: int * const p;

限制指针变量本身的意思是,指针变量本身的值不能被修改,但指向的内容可以改变。所以
被 const 修饰的指针变量指针只能在定义时初始化,不能定义之后再赋值,代码如下:
在这里插入图片描述
限制指针变量指向的数据

如: const  int *ip; int const *ip;

上面两种写法都可以,一般使用第二种,限制指针变量指向的数据的意思就是指针可
以指向不同的变量(指针本身的值可以修改),但是不能用指针修改指针指向的数据的值,
代码如下:
在这里插入图片描述
限制指针变量和指针变量指向的数据的值

如:const int * const ip;

上面这种写法使指针变量和指针变量指向数据的值都不能修改, 代码如下:
因为不能修改指针的值,所以定义的时候必须初始化
在这里插入图片描述
题目:将字符zssss换成li

方法一:直接用strcpy

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string>
int main() {
	char name[10] = "zssss";//字符数组和字符串相互转化
	strcpy(name, "li");
	printf("%s\n", name);
	return 0;
}

在这里插入图片描述

方法二:用strncpy

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string>
int main() {
	char name[10] = "zssss";//字符数组和字符串相互转化
	strncpy(name, "lisi",2);
	printf("%s\n", name);
	return 0;
}

在这里插入图片描述
没有达到预想的结果,添加memset

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string>
int main() {
	char name[10] = "zssss";//字符数组和字符串相互转化
	memset(name, 0, strlen(name));//ASCII码值 0就是'\0'  
	strncpy(name, "lisi",2);
	printf("%s\n", name);
	return 0;
}

说明:这里memset功能是把strlen(name)个字节的元素替换成’\0’

在这里插入图片描述

方法三:

#include <stdio.h>
int main(){
	//char* name= "zssss";//error const char*类型的值不能初始化char*
	const char* name = "zssss";//字符串是一个常量,所以得用常指针
	*name="li";//error 表达式必须是可以修改的左值,说明*name是一个常量
	name = "li";
	printf("%s\n", name);
	return 0;
}

const修饰的是char 类型,字符是常量不能被修改,但是指针name自身是变量,可以被修改(不能直接修改name的值,但可以通过修改name指针自身所储存的地址来修改name的值。)重新指向一个地址
在这里插入图片描述

说明:指向字符串的指针得用常指针

改变一个字符串的值
方法一:如果是常指针,就让该指针指向另一个字符串
方法二:如果是字符数组,用memset(),再用strncpy()
方法三:如果是字符数组,直接用strcpy()

指针和数组的关系

int arr[5]={1,2,3,4,5};
int* p = arr;//arr是首元素的地址
int* p = &arr[0];
p+1 <=> &arr[1];
*(p+1) <=>*&arr[1];

在这里插入图片描述
上图中ar是数组第一个元素的地址,所以ar是数组的第一个元素ar[0],而ar+1是数组第二个元素的地址,(ar+1)是第二个元素ar[1]本身。指针加1,则地址移动一个数组元素所占字节数。

悬挂指针与空指针

悬挂指针:也称为野指针,是没有访问权限的地址。这个地址有可能不存在, 也有可能存
在但是你不能访问。
空指针:NULL,表示当前是一个无效的指针,注意和野指针区分。

指针数组

定义:是一个数组,而且是指针类型的数组,即存放指针变量的数组,存放地址的数组。

const char* str = "hello";//这是一个指向字符的指针;
const char* arr[4] = {"he","wo","ma","an"};//这是一个指针数组,该数组存放了四个指针;

数组指针

定义:是一个指针,也就是地址,该指针指向了一个数组,也就是存放了该数组的地址。

前面学的指针

int arr[] = {1,2,3,4};
int* p = arr;//或者写成int*p = &arr[0] ,p是一个指针变量,指向的是数组arr的地址;
p+1 -> 偏移一个元素的大小 sizeof(int),此时指向了2

注意:数组名arr是一个常量,是数组arr首元素的地址,此时arr <=> &arr[0]

现在学的数组指针

int arr[] = {1,2,3,4};
int[4]* q = &arr// 这样写好理解,但是C语言中写法不是这样
//数组指针可以写成这样:
int(*q)[4] = &arr// 整个arr数组的地址,其实和数组首元素地址一样
//q+1 -> 偏移整个数组的大小 4*sizeof(int) 此时指向了下一个单元

[ ]的优先级高于* ,所以加一个( ),使落脚点是一个指针

测试实例

#include<stdio.h>
//地址 %p 输出默认16进制格式   
int main() {
	int arr[] = {1,2,3,4,5};
	int(*q)[5] = &arr;
	//int(*p)[5] 
	int* p = arr;//int* p = &arr[0];
	printf("p=%d ,arr=%d,&arr[0]=%d,p+1 = %d\n",//十进制
		p,arr,&arr[0],p+1);//输出p存储的地址
	printf("p=%p ,arr=%p,&arr[0]=%p,p+1 = %p\n",//十六进制
		p, arr, &arr[0], p + 1);
	printf("q=%p ,q+1= %p \n", q,q+1);
	return 0;
}

在这里插入图片描述

函数指针和指针函数

函数指针:指向一个函数的指针变量
函数名:函数入口的地址

void funA(int a,char*b){a}
void funB(int a,char*b){b}
void(*pfun)(int ,char*);//函数指针
void(*pfun)(int ,char*) = funA  //函数名:函数入口的地址
void (*signal(int sig,void(*func)(int)))(int);
void(*func)(int)//是一个函数指针,指向函数func的指针,func返回值void,参数int
void(*signal(int,函数指针)(int)
//把signal(int,函数指针)看成整体。它返回的是返回值是void (int)这样的一个函数

指针函数:落脚点是函数,返回值是指针
类型重定义typedef

typedef unsigned long long nint64;
typedef int(*ArrPointer)[3];
int arr[3]={1,2,3};
int(*p)[3]=&arr;  < = > ArrPointer p = &arr;

指针编程练习

删除多余空格

请实现一个函数,将字符串中连续的空格删除,只保留一个空格,例如”a   b   c  d   ”,结果为”a b c d”。
#include<stdio.h>
#include<stdlib.h>
#include <string>
//时间复杂度O(n^2);最容易想到的
void deleteblank1(char* arr) {
	//用for循环实现:
	for (int i = 0; arr[i] != '\0'; i++) {
		if (arr[i] != ' '|| arr[i] == ' ' && arr[i + 1] != ' ') {
			continue;
		}
		else if (arr[i] == ' ' && arr[i + 1] == ' ') {
			for (int j = i; arr[j] != '\0'; j++) {
				arr[j] = arr[j + 1];
			}	
			i--;//得减一次
		}
	}
	//用while循环实现
	int i = 0;
	while(arr[i] != '\0') {
		if (arr[i] != ' ' || arr[i] == ' ' && arr[i + 1] != ' ') {
			i++;
		}
		else if (arr[i] == ' ' && arr[i + 1] == ' ') {
			for (int j = i; arr[j] != '\0'; j++) {
				arr[j] = arr[j + 1];
			}
		}
	}
}
//时间复杂度为O(n) 第一次实现
void deleteblank2(char* arr) {
	int i = 0, j = 0;
	while (arr[i]!='\0')
	{
		if (arr[i]!=' ') {
			i++; j++;
		}
		else if (arr[i] == ' ' && arr[i + 1] == ' ')i++;
		else if (arr[i] == ' ' && arr[i + 1] != ' ') {
			arr[j] = arr[i]; i++; j++;
			arr[j] = arr[i];
			i++; j++;
		}
	}
	arr[j] = '\0';
}
//时间复杂度为O(n) 在deleteblank2上简化
void deleteblank3(char* arr) {
	int i = 0, j = 0;
	while (arr[i] != '\0')
	{
		if (arr[i] == ' ' && arr[i + 1] == ' ')i++;
		else {
			arr[j++] = arr[i++];
		}
	}
	arr[j] = '\0';
}
//时间复杂度为O(n) 根据deleteblank3用指针实现
typedef char* PChar;//运用typedef
void deleteblank4(char* arr) {
	PChar p = arr, q = arr;
	while (*p)
	{
		if (*p == ' ' && *(p + 1) == ' ') {
			p++;
		}
		else {
			*q++ = *p++;
		}
	}
	*q = '\0';
}
//测试各个函数
int main() {
	char arr[] = "a  b    c      d";
	deleteblank1(arr);
	printf("%s\n", arr);
	char brr[] = "a b  c  d";
	deleteblank2(brr);
	printf("%s\n", brr);
	char crr[] = "a         b    c      d";
	deleteblank3(crr);
	printf("%s\n", crr);
	char drr[] = "a        b    c      d         ";
	deleteblank4(drr);
	printf("%s\n", drr);
	return 0;
}

在这里插入图片描述

交换后几个数到前面

有n个整数,使前面各数顺序向后移m个位置,最后m个数变成最前面m 个数,见图。写一函数实现以上功能,在主函数中输入n个整数和输出调整后的n个数。
示意图

//交换两个数的位置
void reverse(int* arr, int begin, int end) {
    int* p = arr + begin;
    int* q = arr + end;
    int temp;
    while (p < q) {
        temp = *p;
        *p = *q;
        *q = temp;
        p++;
        q--;
    }
}
void adjust(int* arr, int len, int m) {
    assert(arr != NULL && len >= m);
    reverse(arr, 0, len - 1);
    reverse(arr, 0, m - 1);
    reverse(arr, m, len - 1);
}
int main() {
    int arr[] = { 1,2,3,4,5 };
    adjust(arr, 5, 2);
    for (int i = 0; i < 5; i++) {
        printf("%5d", arr[i]);
    }
    return 0;
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值