C语言指针知识梳理
文章目录
前言
随着c语言学习的不断深入,指针的应用也更加的频繁,温故而知新,希望通过简单的知识梳理能够帮助大家更好的理解和把握指针。
一、什么指针?
为了说清楚什么是指针,必须先弄清楚数据在内存中是如何存储的,又是如何读取的。如果在程序中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配内存单编译系统根据程序中定义的变量类型,分配一定长度的空间。内存区的每一个字节有一个编号,这就是“地址”,它相当于旅馆中的房间号。在地址所标志的内存单元中存放的数据则相当于旅馆房间中居住的旅客。
由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。打个比方,一个房间的门口挂了一个房间号2008,这个2008就是房间的地址,或者说,2008“指向”该房间。因此,将地址形象化地称为“指针”。意思是通过它能找到以它为地址的内存单元。
即:一个变量的地址就是指针
直接寻址和间接寻址
直接寻址:通过变量名直接访问到内存空间的过程称为直接寻址
间接寻址:通过指针变量间接访问到对应变量的内存空间的过程称为间接寻址
概念
指针类型:C语言的一种数据类型,用于保存变量的内存地址的一种数据类型,并且可以通过这种类型的变量间接的访问到对应的存储空间。
变量的内存地址:该变量的第一个字节的内存地址,在32位编译器下,内存中一个字节的地址是32位的。意味着内存地址的取值范围:0x0000 0000 ~ 0xFFFF FFFF(4GB),说明当一个变量存取值的时候都需要找到该变量的内存地址来进行操作。
我们可以使用&变量名的方式来获取一个变量的地址
二、指针变量
2.1指针变量概念
指针类型的变量,用于保存一个变量的内存地址,并且可以通过这个地址进行特殊的计算,找到对应变量的存储空间
2.2定义方式
语法格式:
① 声明
基类型* 指针变量名;
② 初始化
基类型* 指针变量名 = &值;
③ 赋值
指针变量名 = &值;
2.3指向变量的指针变量
如果一个指针变量保存了指定的变量的内存地址,我们就称这个指针变量指向了那个变量。
int a=100;
int* p;
p = &a;
三 指针常量和常量指针
const关键字修饰指针变量会根据修饰的目标不同导致两种结果
3.1 指针常量
指针的指向不能修改,即该指针变量中保存的内容不可改。
p不能修改,p可以修改
语法格式:
基类型 const 指针变量名
实际中注意以下示例
int b = 88;
//指针常量
int* const p = &b;
//只读变量,无法直接被修改,p本身不可修改,p的指向不能变
3.2 常量指针
指针的指向可以修改,即指针变量中保存的值可以改,但是指针所指向的空间中的内容是不可以改的
p可以修改,p不能修改
语法格式
const 基类型 指针变量名;
基类型 const * 指针变量名;
int a = 1;
int const* p = &a;
p = &b;
*p = 9;//*p的数据类型是const int类型,p本身是可以随便指向,*p是不可直接修改的
四 空指针和野指针,指针传参
4.1 空指针(void)
void通常出现在函数的返回值类型声明。是指函数不需要返回,其次就是函数不需要接收参数时,形式参数列表可以用void填充int fun(void);
void类型也可以作为指针的基类型,目的是适配所有数据类型,因为void本身就是适配所有数据类型的意思,一个void类型的变量是无法声明的,因为void代表无类型。
因此使用void来充当指针变量的基类型,就可以让这个指针变量来存储任意变量的地址。我们也称这种类型的指针变量为“泛类型指针”
优点:可以接收任意变量的地址,也可以充当函数参数接收任意变量的地址;可以转换成任意基类型的指针类型,参与运算
综上所述:基类型是void的指针变量可以接收任意格式的地址,当使用void类型的指针变量时需要强制类型转换成其他具体的数据类型。否则会出风险。
即:在函数具体的形式不定时可以先定义空,根据后续的具体情况进行更改。
4.2野指针
声明一个指针变量但是没有初始化,意味着该指针变量的指向是随机的,此时直接使用指针变量来访问对应的内存地址,会产生不可预料的错误,我们称这种未被初始化的指针变量为“野指针”。为了防止这种现象产生,在指针初始化时,建议将该指针变量置为空
基类型* 指针变量名=NULL;
此时,该指针变量被称为空指针,不指向任何有效变量。其危害巨大新学者在学习和使用指针时要多加注意。
4.3 指针传参
1.指针变量充当函数形式参数
实际上就是将地址传递给形式参数,此时函数中的形式参数的这个指针也就指向了实际参数所在的空间,所以就可以通过形式参数来间接的修改实际参数的值
2.数组传参
函数的参数传递过程中,如果想要将数组当做参数传递给函数,编译器是无法实现的。因为数组空间比较大,如果要实现将整个数组进行参数传递,则需要耗费大量的空间由主调函数复制一个数组的备份交给被调函数。因此编译器并不会采取将整个数组进行传递的过程;而是将数组的首地址进行传递。 函数的调用时,如果要使用数组进行参数的传递,实际参数填入数组名(数组首地址),形式参数需要写一个指针,用于接收实际参数。
五 动态内存分配
5.1 动态内存分配函数
注:由于在学习过的动态内存分配中经常牵扯,用相应类型的指针来存储申请空间的问题,所以适当补充其相关知识也就有十足的必要性。
其主要相关函数有· malloc realloc calloc free
特别注意一般程序中在free相应空间时,即失去了对空间的掌控权,应及时将ptr置空,防止野指针。 free的崩溃也通常由于多次释放和非法释放引起。
free函数知识点补充
Free如何知道要释放内存的大小
当你调用 malloc、calloc 或 realloc 函数分配内存时,这些函数会在内存中找到一个足够大的连续空间,并返回一个指向这块内存的指针。
这些函数会记录下分配的内存块的大小,并将这个信息存储在内存块的元数据中(通常在内存块的开始位置)。
当你调用 free 函数时,只需要传递之前分配的内存块的指针。操作系统会通过这个指针找到相应的内存块,并从元数据中获取内存块的大小,然后释放这块内存。
六 数组指针和指针数组 以及函数指针
6.1 数组指针
概念:用于保留一个数组长度大小的变量的地址的变量,该指针变量的格式为
数据类型(* 指针变量名)[长度];
代表该指针变量指向了一个二维数组中的一维数
本质还是指针
char(*ptr)[10]=NULL;
char s[10]="hello";
ptr=&s;
其中ptr+1会加多少字节诸位可以想想。
6.2 指针数组
概念:数组中的元素都是指针变量
语法结构:
声明–
基类型* 数组名[长度];
char * str[3]={"hello","world","free"};
str[0] str[1] str[2]
假定地址为 0x100 0x200 0x300
即:看似数组内的是字符串,但实际只是其首地址
即数组的所有元素都是指针。 str[1]指向字符串world
6.3 函数指针
概念:一个指针变量保存了函数的入口地址(函数名)
语法格式
返回值类型 (*指针变量名)(形参列表) = 函数名;
int add(int a,int b){return a+b};
int sub (int a,int b){ return a-b };
int main()
{
int (*pfun)(int ,int)=NULL;
int x=10,y==20;
int z=0;
pfun=add;
z=pfun(x,y);//新的调用方式
z=(*pfun)(x,y)://旧的调用方式
pfun=sub;
z= pfun(x,y);
return 0;
}
七 二级指针
概念:就是指针变量的地址。使用一种新的指针类型变量来保存一个一级指针变量的地址。
语法:
声明
基类型* 指针变量名;
如下图所示
八 STR系列函数
8.1strcpy(copy)
函数 原型 char *strcpy(char *dest, const char *src);
dest是指向目标字符串的指针,
src是指向源字符串的指针。
strcpy函数从src指向的位置开始复制字符,直到遇到源字符串的空字符(‘\0’),然后它在目标字符串的末尾也放置一个空字符。
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello, World!";
char dest[20]; // 确保有足够大的空间
strcpy(dest, src); // 将src复制到dest
printf("Copied string: %s\n", dest);
return 0;
}
8.2strcmp(compare)
函数原型 int strcmp(const char *str1, const char *str2);
str1和str2都是指向字符的指针,指向需要比较的两个字符串。
函数返回值
如果str1小于str2,返回负数。
如果str1等于str2,返回0。
如果str1大于str2,返回正数。
#include <stdio.h>
#include <string.h>
int main() {
const char *str1 = "example";
const char *str2 = "Example";
int result = strcmp(str1, str2); // 结果将是负数,因为'e'(小写)的ASCII值小于'E'(大写)
if (result < 0) {
printf("First string is less.\n");
} else if (result > 0) {
printf("First string is greater.\n");
} else {
printf("Strings are equal.\n");
}
return 0;
}
8.3strlen(length 用于获取长度)
函数原型 size_t strlen(const char *str)
str是一个指向字符的指针,指向需要计算长度的字符串。
注:strlen函数内部实现是通过遍历字符串,直到**遇到空字符(‘\0’)**来确定字符串的长度。
因此,如果字符串不是以空字符结束的,strlen将无法正确计算长度。
8.4 strcat(cat来源于拉丁语词根“catena”意为“链”或“连接”)
函数原型 char *strcat(char *dest, const char *src);
dest:指向目标字符串的指针,这个字符串需要有足够的空间来存储连接后的字符串。
src:指向要连接的源字符串的指针。
注:使用strcat之前,需要确保dest指向的字符串有足够的空间来存储连接后的字符串,包括源字符串src的字符和结尾的空字符(‘\0’)。如果空间不足,将会导致缓冲区溢出,这是一个严重的安全问题。
strcat函数会覆盖dest字符串末尾的空字符,并在连接后的字符串末尾添加一个新的空字符。
连接操作是在dest字符串的空字符位置开始的,所以dest必须是一个以空字符结尾的有效字符串。
#include <stdio.h>
#include <string.h>
int main() {
char dest[20] = "Hello, ";
const char *src = "World!";
strcat(dest, src); // 将src连接到dest的末尾
printf("Concatenated string: %s\n", dest);
return 0;
}
8.5 strtok(token 片段或者切片)
函数原型 char *strtok(char *str, const char *delim);
str:指向要分割的字符串的指针。如果str是NULL,则strtok会使用它上一次调用时使用的字符串。
delim:一个指向分隔符集合的指针,分隔符用于分割字符串。例:“ ” ,“-”等等
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "one,two,three";
const char *delim = ",";
char *token;
token = strtok(str, delim); // 开始分割字符串
while (token != NULL) {
printf("%s\n", token);
token = strtok(NULL, delim); // 继续分割
}
return 0;
}
MEM系列函数(简写)
memcpy: 复制内存区域。将一块内存区域的内容复制到另一块内存区域。
函数原型 void *memcpy(void *dest, const void *src, size_t n);
memmove: 移动内存区域。类似于memcpy,但允许目标和源区域重叠
函数原型 void *memmove(void *dest, const void *src, size_t n);
memset: 设置内存区域。将一块内存区域的字节设置为特定的值
函数原型 void *memset(void *s, int c, size_t n);
memcmp: 比较内存区域。比较两个内存区域是否相同。
函数原型 int memcmp(const void *s1, const void *s2, size_t n);
总结
提示:这里对文章进行总结:
文章大致讲述了语言指针相关的知识和一些具体应用的介绍,有不足之处望诸位看官多多包含。