目录
指针基础知识
计算机中所有的数据都必须放在内存中,不同类型的数据占用的字节数不一样,例如
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.乘法 1020;
*和&谜题分析
如: 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;
}