1.初识指针🧐
1.1指针是什么?😕
理解的前提:首先我们要知道计算机中的数据都是存放在存储器中,对于正在执行的程序,其指令和数据一般存放在内存上,在c语言(或者计算机中)不同类型的数据所占用的字节数不同。例如char型数据在内存中占1字节,int占4个字节(对于32位编译环境)。同时我们为了正确去访问这些数据,就必须对每个字节编号,类似门牌、身份证这种,每个字节对应的编号唯一。同理也就可以根据编号找到某个字节所存放的数据。
1个字节等于8位哈 1B = 8bit
所以什么是指针?指针也就是内存地址,通俗讲就是上面说的对于内存中字节的编号,也可以叫做是地址
🐾插入一条知识点:
- 取地址运算符
&:获得变量的地址
👀下面看一段代码:
#include <stdio.h>
int main ()
{
int testIntValue = 8;
char testCharValue = 'a';
int *pointInt; // 定义一个整型数据的指针变量
char *pointChar; // 定义一个字符型数据的指针变量
pointInt = &testIntValue; //取地址赋值给对应类型指针
pointChar = &testCharValue; //同理
printf("testIntValue 变量的地址: %p\n", pointInt);
printf("testCharValue 变量的地址: %p\n", pointChar);
return 0;
}
运行结果:
testIntValue 变量的地址: 000000000061FE0C
testCharValue 变量的地址: 000000000061FE0B
注意😲
- 要记住,上面的地址只是一个字节的编号。所以对于
testIntValue这个变量的值8并不是都存在了其中。这里若int位4个字节,则数值8是一共要占4个字节,也就是四个地址。通俗理解住四间房 - 我们把变量所占存储空间的首地址成为变量的地址,所以通过
pointInt这个指针能访问到testIntValue变量所对应的值8 - 其实变量名、函数名这些只是地址的一种助记符,是对数据存储空间的一种抽象。
1.2指针变量🏃♂️
既然指针就是内存地址,那么指针变量就是用来存放内存地址的变量。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
type *var_name;
type类型关键字,即指针的基类型。代表指针变量要指向的变量的数据类型。var_name指针的变量名。- 这里
*用来说明他是一个指针类型变量
1.3指针的基本操作😴
1.3.1 通过指针访问变量的值🙆♂️
前面提到过,指针变量存储的是变量的地址,通过指针访问变量的值需要使用*运算符,即间接访问运算符(indirection operator)也叫解引用运算符。以下是一个例子:
#include <stdio.h>
int main()
{
int var = 10;
int *ptr = &var; // ptr指向var变量的地址
printf("var的值:%d\n", var); // 输出10
printf("*ptr的值:%d\n", *ptr); // 输出10
printf("ptr指针所指向地址的变量的值:%d\n", *ptr); // 输出10
printf("var变量的地址:%p\n", &var); // 输出var的地址
printf("ptr指针存储的地址:%p\n", ptr); // 输出ptr所存储的地址
return 0;
}
输出结果:
var的值:10
*ptr的值:10
ptr指针所指向地址的变量的值:10
var变量的地址:0x7ffcc2af3aa8
ptr指针存储的地址:0x7ffcc2af3aa8
可以看到,通过*ptr访问指针所指向的变量,也就是var的值,输出结果是10。注意,*ptr这种写法实际上是在使用指针变量ptr存储的地址去访问其所指向的变量var的值,而不是访问指针本身的值。
1.3.2 修改指针所指向的变量的值🎐
通过指针修改变量的值,也需要使用间接访问运算符。例如:
#include <stdio.h>
int main()
{
int var = 10;
int *ptr = &var; // ptr指向var变量的地址
printf("var的值:%d\n", var); // 输出10
*ptr = 20; // 使用指针修改var的值
printf("var的值:%d\n", var); // 输出20
return 0;
}
输出结果:
var的值:10
var的值:20
1.3.3 指针的算术运算😋
指针在内存中也是以地址的形式存储,因此可以进行指针的算术运算。常见的指针运算有以下几种:
- 指针加上一个整数:指针会向后移动多少个字节,移动的大小取决于指针所指向的变量的数据类型。例如,
ptr+1会使指针ptr向后移动4个字节,因为在32位的机器上,一个int类型占4个字节。 - 指针减去一个整数:同理,指针会向前移动多少个字节,移动的大小取决于指针所指向的变量的数据类型。
- 两个指针相减:两个指针相减,得到的结果为它们之间相隔的元素个数,这里的元素个数也是取决于指针所指向的变量的数据类型。
需要注意的是,在指针运算中,不要访问非法的内存地址,否则会导致程序崩溃或出现其他问题。
下面看一个指针运算的例子:
#include <stdio.h>
int main ()
{
int arr[] = {10, 20, 30, 40, 50};
int *ptr1 = &arr[0], *ptr2 = &arr[4];
int n;
printf("ptr2 - ptr1 = %td\n", ptr2 - ptr1); //输出为4,因为两个指针相差4个元素
n = ptr2 - ptr1; // n = 4;
printf("*ptr1 = %d\n", *ptr1); //输出10
printf("*(ptr1+2) = %d\n", *(ptr1+2)); //输出30
printf("*ptr2 = %d\n", *ptr2); //输出50
printf("*(ptr2-2) = %d\n", *(ptr2-2)); //输出30
return 0;
}
输出结果:
ptr2 - ptr1 = 4
*ptr1 = 10
*(ptr1+2) = 30
*ptr2 = 50
*(ptr2-2) = 30
从输出结果中可以看出,ptr2 - ptr1的值为4,即ptr2和ptr1之间相差4个元素;通过ptr1+2可以访问到arr[2]元素的值30,通过ptr2-2可以访问到arr[2]元素的值30。
指针运算可以灵活地访问数组元素,非常适合数组的遍历和访问。但需要注意的是,指针运算必须要保证不访问非法的内存地址,否则会导致程序出现错误。
1.3.4 NULL指针😫
即空指针。在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。
NULL 指针是一个定义在标准库中的值为零的常量。
#include <stdio.h>
int main ()
{
int *ptr = NULL;
printf("ptr 的地址是 %p\n", ptr );
return 0;
}
运行结果:
ptr 的地址是 0x0
在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。
如需检查一个空指针,您可以使用 if 语句,如下所示:
if(ptr) /* 如果 p 非空,则完成 */
if(!ptr) /* 如果 p 为空,则完成 */
1.3.4部分来自菜鸟教程 C指针节 C 中的 NULL 指针 板块
1.4指针和数组🤐
数组名实际上就是数组元素的首地址,因此可以将数组名赋值给指针变量。例如:
int arr[3] = {10, 100, 200};
int *ptr = arr;
在上述代码中,arr是一个整型数组,我们定义了一个指向整型数据的指针变量ptr。可以看出,我们将arr的地址赋值给了ptr,因此ptr现在指向数组arr的第一个元素。
可以通过使用数组名和指针变量来访问数组中的元素。例如:
printf("第一个元素的值是 %d\n", *ptr );
printf("第二个元素的值是 %d\n", *(ptr+1) );
printf("第三个元素的值是 %d\n", *(ptr+2) );
输出结果:
第一个元素的值是 10
第二个元素的值是 100
第三个元素的值是 200
再次提示这里使用了解引用运算符*,因为指针本身存储的是地址,需要通过解引用运算符*来访问指向的实际数据。同时,指针也可以进行算术运算,例如指向数组中的下一个元素,可以使用指针加1来实现。这就是上面代码中*(ptr+1)的意义。结合上面指针的算术运算理解
1.5指针和函数😥
指针还可以作为函数的参数来传递地址,从而达到在函数内部改变外部变量的值的目的。例如:
#include <stdio.h>
void swap(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}
int main() {
int a = 100;
int b = 200;
printf("交换前:a = %d, b = %d\n", a, b);
swap(&a, &b);
printf("交换后:a = %d, b = %d\n", a, b);
return 0;
}
输出结果:
交换前:a = 100, b = 200
交换后:a = 200, b = 100
在上述代码中,我们定义了一个swap()函数,该函数接受两个指向整型数据的指针变量x和y作为参数,通过指针交换x和y所指向的数据。在main()函数中,我们定义了两个整型变量a和b,然后调用了swap()函数,并将a和b的地址作为参数传递给该函数,从而实现了在函数内部交换a和b的值的目的。
**思考/回忆:**如果将上面代码改成下面,则运行结果是什么?为什么?
这里回答有奖励哦!!!大家快响应,理一理我!!!✨🎉🍗
#include <stdio.h>
void swap(int x, int y) {
int temp = x;
x = y;
y = temp;
}
int main() {
int a = 100;
int b = 200;
printf("交换前:a = %d, b = %d\n", a, b);
swap(a, b);
printf("交换后:a = %d, b = %d\n", a, b);
return 0;
}
1.6 函数指针及其应用(慢慢琢磨)😡
函数指针是指向函数的指针变量,它可以用来存储函数的地址,进而通过该指针调用函数。函数指针在C语言中具有重要的应用,它可以用来实现回调函数、函数参数多态性等高级应用。
1.6.1 基本概念🥱
1.6.1.1 定义函数指针🏷
定义函数指针的一般形式为:
return_type (*pointer_name)(param_list);
其中,return_type为函数返回值类型,pointer_name为函数指针变量名,param_list为函数参数列表。
例如,定义一个返回int类型、带有两个int类型参数的函数指针变量可以这样写:
int (*p)(int, int);
1.6.1.2 函数指针的赋值☁
函数指针变量可以像其他指针变量一样赋值。下面的示例代码演示了如何将函数的地址赋值给函数指针变量:
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int main()
{
int (*p)(int, int);
p = add; // 将函数add的地址赋值给函数指针变量p
int result = p(2, 3); // 通过函数指针调用函数
printf("%d\n", result); // 输出结果5
return 0;
}
1.6.1.3 函数指针的使用✏
函数指针可以像函数名一样直接调用函数,也可以作为参数传递给其他函数。下面的示例代码演示了如何将函数指针作为参数传递给其他函数:
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int calculate(int (*p)(int, int), int a, int b)
{
return p(a, b);
}
int main()
{
int result = calculate(add, 2, 3); // 将函数指针变量add作为参数传递给calculate函数
printf("%d\n", result); // 输出结果5
return 0;
}
另外函数指针含有很多高级应用方式,比如回调函数、函数指针数组、函数指针作为函数参数、函数指针作为返回值、函数指针与结构体,理解这些你的档次将提升N个level,可以大大提高C语言程序的灵活性和可扩展性。(
或许你就不是一个和我一样没有对象的野指针了😭😭😭
2. 字符串😨
毁灭吧,累了,简单整点字符串,睡觉了咩👻👻👻
在C语言中,字符串实际上是一个字符数组,由若干个字符组成,以\0(null字符)结尾。因此,字符串在C语言中是以字符数组的形式存储的。
2.1 字符串的定义🤕
字符串的定义有以下两种方式:
- 使用字符数组定义字符串
char str[] = "Hello, world!";
- 使用指针定义字符串
char *str = "Hello, world!";
需要注意的是,使用指针定义字符串时,字符串常量被存储在只读数据段,不允许修改。因此,如果需要修改字符串,应该使用字符数组定义。
2.2 字符串的常见操作😇
C语言提供了许多字符串操作函数,常见的有:
strlen():获取字符串长度。strcpy():将一个字符串复制到另一个字符串中。strcat():将两个字符串连接起来。strcmp():比较两个字符串是否相等。strchr():在一个字符串中查找某个字符第一次出现的位置。
下面是一些常见的字符串操作示例:
#include <stdio.h>
#include <string.h>
int main()
{
char str1[20] = "Hello";
char str2[20] = "World";
char str3[20];
// 获取字符串长度
printf("str1长度: %ld\n", strlen(str1));
// 复制字符串
strcpy(str3, str1);
printf("将str1复制到str3: %s\n", str3);
// 连接字符串
strcat(str1, str2);
printf("将str2拼接到str1: %s\n", str1);
// 比较字符串
int cmp = strcmp(str1, str2);
if (cmp == 0) {
printf("str1等价str2\n");
} else {
printf("str1不等价str2\n");
}
// 查找字符
char *pos = strchr(str1, 'l');
printf("'l'在str1中位置: %ld\n", pos - str1);
return 0;
}
输出结果为:
str1长度: 5
将str1复制到str3: Hello
将str2拼接到str1: HelloWorld
str1不等价str2
'l'在str1中位置:: 2
需要注意的是,在使用字符串操作函数时,应该确保目标字符串有足够的空间来存储结果。
就到这了,下次再说,下班!