1.指针
1.1 指针的理解:
1.1.1变量的访问方式
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 ,为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元通常占用1个字节。
变量在内存中分配空间,不同类型的变量占用不同大小的空间,那如何访问内存中的变量数据呢?有两种方式:
(1)直接访问,直接使用变量名进行的访问,以前的程序中都是采用这种方式。
(2)间接访问,通过指针来实现。
1.1.2 内存地址
为了能够有效地访问到每个内存单元,就给内存单元进行了编号,这些编号被称为内存地址,因为每个内存单元都有地址,所以变量也是有地址的。
假设有int型变量num,其在内存中会占用4个字节,也就是占用4个内存单元,第 一个内存单元的地址即是变量num的地址。如图;
在32位系统中,内存地址通常是32位二进制数字,即4个字节,这允许寻址2^32(大约4GB)个内存位置。
在64位系统中,内存地址通常是64位二进制数字,即8个字节,这允许寻址2^64个 内存位置,这个数量相当大,远远超过了通常需要的内存大小。
1.1.3什么是指针?
如果一个变量专门用来存放内存地址,则它称为指针变量,通常简称为指针。我们可以 通过指针间接访问内存中另一个数据。
如图,指针里面存储的是变量 num 的地址,我们可以说该指针指向变量 num,通过该指针可以间接访问变量 num。
1.1.4指针的定义
一般格式:
数据类型是指针所指向的地址处的数据类型,如 int、char、float 等。
符号 * 用于通知系统,这里定义的是一个指针变量,通常跟在类型关键字的后面,表 示指针指向的是什么类型的值。比如,char * 表示一个指向字符的指针,float* 表示一个 指向浮点数的指针。
需要注意的是,以下三种定义指针的写法都是合法的:
1.1.5取址运算符和取值运算符
取址运算符,使用&符号表示,作用是取出变量的内存地址。如果要格式化输出地 址,需使用格式占位符%p。
取值运算符,使用*符号表示,作用是取出指针指向的内存地址处的数据值,也称为 解引用运算符或间接引用运算符。
1.1.6指针应用案例
1)案例1
创建一个int类型的变量,使用取址运算符取出其地址,并将其地址赋值给一个指针, 然后分别打印变量的值、变量的地址、指针的值、指针的地址、指针指向的值。
#include<stdio.h>
int main()
{
// 定义变量
int num = 100;
// 定义一个指针,指向变量num的地址
// 这里使用 & 符号取出变量num的地址,在赋值给变量num
int *ptr = #
// 打印变量 num 的值和地址
printf("num变量的值: %d \n", num);
printf("num变量的地址: %p \n", &num);
// 打印 ptr 的值、ptr的地址、ptr指向的值
printf("ptr的值: %p \n", ptr);
printf("ptr的地址: %p \n", &ptr);
printf("ptr指向的值: %d \n", *ptr);
return 0;
}
输出结果:
2)案例二
通过指针修改所指向的值,代码如下:
#include<stdio.h>
int main()
{
// 创建double 类型的变量
double num = 2.88;
// 创建指针p1,指向变量num
double *p1 = #
// 创建指针p2,将p1的值赋值给p2, p1存储的是num的值,此时p1和p2都指向num
double *p2 = p1;
// 打印num的值
printf("%.2f \n", num);
// 通过指针p1修改num的值
*p1 = 3.88;
printf("%.2f \n", num);
// 通过指针p2修改num的值
*p2 += 10;
printf("%.2f \n", num);
return 0;
}
输出结果:
2.1指针运算
2.2.1指针加减整数
指针与整数的加减运算,表示指针所指向的内存地址的移动(加,向后移动;减,向前 移动),指针移动多少,与指针指向的数据类型有关,数据类型占据多少个字节,每单位就 移动多少个字节,比如一个int类型指针,+1向后移动4个字节,-2向前移动8个字节。
数组的元素在内存中连续存储的,我们通过数组元素来演示指针加减整数的情况。
代码如下:
#include<stdio.h>
int main()
{
// 创建数组
int nums[] = {10,20,30,40,50};
// 创建指针指向数组第一个元素的地址
int *ptr = &nums[0];
// 打印指针的值和指向的值
printf("ptr=%p, ptr=%d \n", ptr, *ptr );
// 指针加3,指针指向int类型,每个占四个字节,此时指针会向后移动12个字节
ptr +=3;
printf("ptr=%p, ptr=%d \n", ptr, *ptr);
// 指针-2,此时指针会向前移动8个字节
ptr -=2;
printf("ptr=%p, ptr=%d \n", ptr, *ptr);
return 0;
}
运行结果:
2.2.2指针自增自减
指针自增、自减本质上就是指针加减整数,自增地址后移,自减地址前移。下面我们利用指针的自增自减实现数组的遍历,代码如下:
#include<stdio.h>
int main()
{
// 创建数组,元素都是short类型,占据两个字节
short nums[] = {10,20,30,40,50};
// 定义数组的长度
const int len = 5;
// 利用指针自增遍历数组元素
// 创建指针并指向数组的第一个元素的地址
short *ptr = &nums[0];
// 循环
for(int i = 0; i < len; i++)
{
printf("元素索引:%d, 元素地址:%p, 元素值:%d \n", i, ptr, *ptr);
ptr++;
}
printf("\n");
// 利用指针自减遍历数组
// 如果--在printf后面会超出数组界限,需先自减一次
for(int i = 4; i >= 0; i--)
{
ptr--;
printf("元素索引:%d, 元素地址:%p, 元素值:%d \n", i, ptr, *ptr);
}
return 0;
}
输出结果:
2.2.3同类型指针相减
相同类型的指针可以进行减法运算,返回它们之间的距离,即相隔多少个数据单位。高 位地址减去低位地址,返回的是正值;低位地址减去高位地址,返回的是负值。
同类型指针相减的结果是一个ptrdiff_t类型数据,ptrdiff_t类型是一个带符号 的整数,格式输出中对应的格式占位符是%td,相关案例如下:
#include<stdio.h>
int main()
{
// 创建数组
int nums[] = {10,20,30,40,50};
// 创建指针并指向数组第一个元素的地址
int *ptr1 = &nums[0];
// 创建指针并指向数组第四个元素的地址
int *ptr2 = &nums[3];
printf("ptr2-ptr1=%td \n", ptr2-ptr1);
printf("ptr1-ptr2=%td \n", ptr1-ptr2);
// 在创建两个指针相减
double d1 = 1.0;
double d2 = 2.0;
double *p1 = &d1,*p2 = &d2;
printf("p2-p1=%td \n", p2-p1);
printf("p1-p2=%td \n", p1-p2);
return 0;
}
运行结果:
2.2.4指针的比较运算
指针之间可以进行比较运算,如 ==、、 >=,比较的是各自指向的内存地 址的大小,返回值是 int 类型整数 1 (true)或 0 (false)。案例演示如下:
#include<stdio.h>
int main()
{
// 创建数组
int nums[] = {10,20,30,40,50};
double n = 1.0;
// 创建指针并指向数组的第一个元素被
int *ptr1 = &nums[0];
// 创建指针并指向数组的第四个元素被
int *ptr2 = &nums[3];
// 创建指针也指向数组的第一个元素被
int *ptr3 = &nums[0];
// 创建指针指向变量n的地址
double *ptr4 = &n;
// 输出指针指向的地址
printf("ptr1=%p \n", ptr1);
printf("ptr2=%p \n", ptr2);
printf("ptr3=%p \n", ptr3);
printf("ptr4=%p \n", ptr4);
// 进行比较
printf("ptr1>ptr2: %d \n", ptr1 > ptr2);
printf("ptr1<ptr2: %d \n", ptr1 < ptr2);
printf("ptr1==ptr3: %d \n", ptr1 == ptr3);
printf("ptr4>ptr1: %d \n", ptr4 > ptr1); /// 由于是不同类型进行比较,所以会有一个警告
return 0;
}
运行结果:
3.1指针和数组
3.3.1数组名
数组名在大多数情况下会被隐式地转换为指向数组第一个元素的指针,在特定情况下数组名可以被视为一个指针,具有一些指针的特性。 但是数组名与真正的指针是不同的,主要有以下几点区别:
(1)使用sizeof运算符,数组名得到的是整个数组的大小;指针得到的是本身的大小。
(2)数组名不能进行自增、自减运算。
(3)数组名的指向不可更改。
示例代码:
#include<stdio.h>
int main()
{
// 创建数组
int nums[] = {10,20,30,40,50};
// 创建指针指向数组的第一个元素
int *ptr = &nums[0];
// 数组名中存储了第一个元素的地址
printf("%p, %d \n", nums, *nums);
printf("%p, %d \n", ptr, *ptr);
// nums 和 ptr 比较
if(nums == ptr)
{
printf("nums 和 ptr 相等 \n\n");
}
else
{
printf("nums 和 ptr 不相等 \n\n");
}
// 数组名和指针真正的区别
// 1. sizeof 运算符返回的是整个数组的大小,而指针返回的是本身的大小。
printf("%zu, %zu \n", sizeof nums, sizeof ptr);
// 2. 数组名不能进行自增自减运算
// nums++-----报错
ptr++;
printf("%p, %d \n", ptr, *ptr);
// 3. 数组名的指向不能修改
int n = 100;
// nums = &n;
ptr = &n;
printf("%p, %d \n", ptr, *ptr);
return 0;
}
运算结果:
3.3.2指针数组
指针数组(PointerArray)是一个数组,其中的每个元素都是指针。
语法规则:
示例代码:
#include<stdio.h>
int main()
{
// 创建三个变量
int num1 = 10, num2 = 20, num3 = 30;
// 创建一个长度为3的指针数组
int *ptrArr[3];
// 指针数组的每个元素指向不同的整数
ptrArr[0] = &num1;
ptrArr[1] = &num2;
ptrArr[2] = &num3;
// 遍历指针数组
for(int i = 0; i < 3; i++)
{
printf("%d, %p, %d \n", i, ptrArr[i], *ptrArr[i]);
}
return 0;
}
输出结果:
3.3.3数组指针
数组指针(ArrayPointer)是一个指针,它指向一个数组。注意,数组指针指向的是整个数组的地址而不是第一个元素的地址,虽然二者值是相同的,但在运算中会表现出不同。
语法规则:
示例代码:
#include <stdio.h>
int main()
{
//创建整数数组
int arr[5]= {10, 20, 30, 40, 50};
//创建指针指向数组arr &arr表示整个数组的地址
int(*ptr)[5] = &arr; //int(*ptr)[5]=arr; //会有警告,arr的类是int*, ptr类型是int(*)[5]
//二者值是相同的
printf("arr=%p \n", arr);
printf("ptr=%p \n\n", ptr);
//数组指针指向的是数组的地址,而不是第一个元素的地址
//数组指针+1会向后移动4*5=20个字节;数组+1会向后移动4个字节
printf("arr+1=%p \n", arr + 1);
printf("ptr+1=%p \n\n", ptr + 1);
//使用数组指针遍历数组
for (int i= 0; i < 5; i++)
{
printf("%d \n", (*ptr)[i]);
}
return 0;
}
运算结果:
数组指针和数组名的区别:
(1)指向不同:数组名指向元素首地址,数组指针指向数组的地址。
(2)类型不同:上面案例中,arr 的类型是 int[5],;ptr 的类型是 int(*)[5]。
(3)可变性:数组名通常是不可变的;数组指针是可变的,你可以将它指向不同的数 组。
(4)初始化:数组名不需要显式初始化,它会自动指向数组的首元素;数组指针需要显式初始化,指定它将指向的数组。
(5)访问元素:数组名访问数组元素不需要解引用;数组指针通常需要解引用才能访问数组元素。
3.3.4字符指针
1)基本介绍
字符指针变量(简称字符指针)是C语言中的一种指针类型,它用于指向字符或字符串(字符数组),通常用于处理字符串(字符数组)。
2)用字符指针指向一个字符串
C语言对字符串"hellotom"是按字符数组处理的,在内存中开辟了一个字符数组用来存放字符串,程序在定义字符串指针pStr时只是把字符串首地址(即存放字符串的字符数组的首地址)赋给pStr。
3)字符数组名和字符指针表示字符串的区别
(1)对字符数组只能对各个元素赋值,不能对字符数组名重新赋值。
(2)字符指针是可变的,允许使用下面方法重新赋值,指向新的字符串。
4.1指针和函数
4.4.1传递指针给函数
当函数的形参类型是指针类型时,使用该函数时,需要传递指针、地址或者数组给该形 参。
1)传地址或指针给函数
示例代码:
2)传数组给函数
数组名本身就代表数组首地址,因此传数组的本质就是传地址。
示例代码:
问:如果在getAverage()函数中,通过指针修改了数组的值,那么main函数中的balance 数组的值是否会相应变化?
答:会的,因为getVerage函数中的指针,指向的就是main函数中的 balance数组。