第一项 内存地址
每一个变量都有一个内存位置,每一个内存位置都定义了可使用 & 运算符访问的地址,它表示了在内存中的一个地址。
#include <stdio.h>
int main ()
{
int var_runoob = 10;
int *p; // 定义指针变量
p = &var_runoob;
printf("var_runoob 变量的地址: %p\n", p);
return 0;
}
var_runoob 变量的地址: 0x7ffeeaae08d8
第二项 C指针
什么是指针?
指针也就是内存地址,指针变量是用来存放内存地址的变量。使用指针存储其他变量地址之前,应对其进行声明。
type *var_name;
type 是指针的基类型,必须是一个有效 C 数据类型,var_name 是指针变量的名称。*是用来指定一个变量指针。
int *ip; /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *ch; /* 一个字符型的指针 */
所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。
不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
如何使用指针
定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址值。
通过使用一元运算符 * 返回位于操作数所指定地址变量值。
eg:
#include <stdio.h>
int main ()
{
int var = 20; /* 实际变量的声明 */
int *ip; /* 指针变量的声明 */
ip = &var; /* 在指针变量中存储 var 的地址 */
printf("var 变量的地址: %p\n", &var );
/* 在指针变量中存储的地址 */
printf("ip 变量存储的地址: %p\n", ip );
/* 使用指针访问值 */
printf("*ip 变量的值: %d\n", *ip );
return 0;
}
var 变量的地址: 0x7ffeeef168d8
ip 变量存储的地址: 0x7ffeeef168d8
*ip 变量的值: 20
NULL指针
NULL 指针是一个定义在标准库中的值为零的常量(即空指针)。
#include <stdio.h>
int main ()
{
int *ptr = NULL;
printf("ptr 的地址是 %p\n", ptr );
return 0;
}
ptr 的地址是 0x0
地址为 0 的内存是不允许访问。
内存地址 0 有特别重要的意义,即表明该指针不指向一个可访问的内存位置。
指针包含空值(零值),则假定它不指向任何东西。
检查空指针
if(ptr) /* 如果 p 非空,则完成 */
if(!ptr) /* 如果 p 为空,则完成 */
详解
概念 | 描述 |
---|---|
指针的算术运算 | 可以对指针进行四种算术运算:++、--、+、- |
指针数组 | 可以定义用来存储指针的数组。 |
指向指针的指针 | C 允许指向指针的指针。 |
传递指针给函数 | 通过引用或地址传递参数,使传递的参数在调用函数中被改变。 |
从函数返回指针 | C 允许函数返回指针到局部变量、静态变量和动态内存分配。 |
指针的算术运算
- 指针的每一次递增,它其实会指向下一个元素的存储单元。
- 指针的每一次递减,它都会指向前一个元素的存储单元。
- 指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度,( int 是 4 个字节)。
递增一个指针
递增一个指针意味着让指针指向下一个内存位置。
指针的递增操作会根据指针所指向的数据类型进行适当的内存偏移。
指针可代替数组,变量指针可递增,数组不能递增,数组可看成一个指针常量。
eg:
#include <stdio.h>
const int MAX = 3;
int main ()
{
// 定义一个整数数组
int var[] = {10, 100, 200};
// 定义一个整数变量 i 和一个整数指针 ptr
int i, *ptr;
// 将指针 ptr 指向数组 var 的起始地址
ptr = var;
// 循环遍历数组
for ( i = 0; i < MAX; i++)
{
// 打印当前指针 ptr 所指向的地址
printf("存储地址:var[%d] = %p\n", i, ptr );
// 打印当前指针 ptr 所指向地址的值
printf("存储值:var[%d] = %d\n", i, *ptr );
// 将指针 ptr 移动到下一个数组元素的位置
ptr++;
}
return 0;
}
存储地址:var[0] = e4a298cc
存储值:var[0] = 10
存储地址:var[1] = e4a298d0
存储值:var[1] = 100
存储地址:var[2] = e4a298d4
存储值:var[2] = 200
递增字符指针:
eg:
#include <stdio.h>
int main() {
char str[] = "Hello";
char *ptr = str; // 指针指向字符串的第一个字符
printf("初始字符: %c\n", *ptr); // 输出 H
ptr++; // 递增指针,使其指向下一个字符
printf("递增后字符: %c\n", *ptr); // 输出 e
return 0;
}
ptr++ 使指针从 str[0] 指向 str[1]。因为 ptr 是一个 char 类型指针,递增时会移动 sizeof(char) 个字节(即 1 个字节)。
初始字符: H 递增后字符: e
递增结构体指针:
eg:
#include <stdio.h>
struct Point {
int x;
int y;
};
int main() {
struct Point points[] = {{1, 2}, {3, 4}, {5, 6}};
struct Point *ptr = points; // 指针指向结构体数组的第一个元素
printf("初始点: (%d, %d)\n", ptr->x, ptr->y); // 输出 (1, 2)
ptr++; // 递增指针,使其指向下一个结构体
printf("递增后点: (%d, %d)\n", ptr->x, ptr->y); // 输出 (3, 4)
return 0;
}
ptr++ 使指针从 points[0] 指向 points[1]。 ptr 是一个 struct Point 类型指针,递增时会移动 sizeof(struct Point) 个字节。
初始点: (1, 2) 递增后点: (3, 4)
递减一个指针
递减一个指针意味着让指针指向前一个内存位置。
对指针进行递减运算,即把值减去其数据类型的字节数
eg:
初始值: 50 递减后值: 40 再次递减后值: 30
递减字符指针:
eg:
#include <stdio.h>
int main() {
char str[] = "Hello";
char *ptr = &str[4]; // 指针指向字符串的最后一个字符 'o'
printf("初始字符: %c\n", *ptr); // 输出 o
ptr--; // 递减指针,使其指向前一个字符
printf("递减后字符: %c\n", *ptr); // 输出 l
ptr--; // 再次递减指针
printf("再次递减后字符: %c\n", *ptr); // 输出 l
return 0;
}
初始字符: o 递减后字符: l 再次递减后字符: l
递减结构体指针:
eg:
#include <stdio.h>
struct Point {
int x;
int y;
};
int main() {
struct Point points[] = {{1, 2}, {3, 4}, {5, 6}};
struct Point *ptr = &points[2]; // 指针指向结构体数组的最后一个元素
printf("初始点: (%d, %d)\n", ptr->x, ptr->y); // 输出 (5, 6)
ptr--; // 递减指针,使其指向前一个结构体
printf("递减后点: (%d, %d)\n", ptr->x, ptr->y); // 输出 (3, 4)
ptr--; // 再次递减指针
printf("再次递减后点: (%d, %d)\n", ptr->x, ptr->y); // 输出 (1, 2)
return 0;
}
初始点: (5, 6) 递减后点: (3, 4) 再次递减后点: (1, 2)
指针的比较
比较指针可确定它们的关系。指针比较用于确定两个指针是否指向相同的内存位置或确定一个指针是否位于另一个指针的前后。
指针可以用关系运算符进行比较,如==
、!=
、<
、>
、<=
和>=
。
指针相等比较:
eg:
#include <stdio.h>
int main() {
int a = 5;
int b = 10;
int *ptr1 = &a;
int *ptr2 = &a;
int *ptr3 = &b;
if (ptr1 == ptr2) {
printf("ptr1 和 ptr2 指向相同的内存地址\n"); // 这行会被输出
} else {
printf("ptr1 和 ptr2 指向不同的内存地址\n");
}
if (ptr1 != ptr3) {
printf("ptr1 和 ptr3 指向不同的内存地址\n"); // 这行会被输出
} else {
printf("ptr1 和 ptr3 指向相同的内存地址\n");
}
return 0;
}
ptr1 和 ptr2 指向相同的内存地址
ptr1 和 ptr3 指向不同的内存地址
指针大小比较:
eg:
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *ptr1 = &arr[1]; // 指向 arr[1],值为 20
int *ptr2 = &arr[3]; // 指向 arr[3],值为 40
if (ptr1 < ptr2) {
printf("ptr1 在 ptr2 之前\n"); // 这行会被输出
} else {
printf("ptr1 在 ptr2 之后或相同位置\n");
}
if (ptr1 > ptr2) {
printf("ptr1 在 ptr2 之后\n");
} else {
printf("ptr1 在 ptr2 之前或相同位置\n"); // 这行会被输出
}
return 0;
}
ptr1 在 ptr2 之前 ptr1 在 ptr2 之前或相同位置
遍历数组并比较指针:
eg:
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *start = arr; // 指向数组的第一个元素
int *end = &arr[4]; // 指向数组的最后一个元素
int *ptr;
for (ptr = start; ptr <= end; ptr++) {
printf("当前指针指向的值: %d\n", *ptr);
}
return 0;
}
当前指针指向的值: 10 当前指针指向的值: 20 当前指针指向的值: 30 当前指针指向的值: 40 当前指针指向的值: 50
总结
- 相等比较 (
==
和!=
): 用于判断两个指针是否指向相同内存位置。 - 大小比较 (
<
,>
,<=
,>=
): 用于指针遍历数组或内存块,判断一个指针是否在另一个指针的b'b'b'b'b'b'b'b'b'b'b'b'b'b'b。
指针比较在指向同一个数组或同一内存块有意义。
指针数组
一个数组,其中每个元素都是指向某种数据类型指针。
存储了一组指针,每个指针可以指向不同数据对象。
用于处理多个数据对象,例如字符串数组或其他复杂数据结构的数组。
#include <stdio.h>
const int MAX = 3;
int main ()
{
int var[] = {10, 100, 200};
int i;
for (i = 0; i < MAX; i++)
{
printf("Value of var[%d] = %d\n", i, var[i] );
}
return 0;
}
Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200
让数组存储指向 int 或 char 或其他数据类型的指针
int *ptr[MAX];
ptr 声明为一个数组,由 MAX 个整数指针组成;ptr 中的每个元素,都是一个指向 int 值的指针。
eg:
#include <stdio.h>
const int MAX = 3;
int main ()
{
int var[] = {10, 100, 200};
int i, *ptr[MAX];
for ( i = 0; i < MAX; i++)
{
ptr[i] = &var[i]; /* 赋值为整数的地址 */
}
for ( i = 0; i < MAX; i++)
{
printf("Value of var[%d] = %d\n", i, *ptr[i] );
}
return 0;
}
Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200
用一个指向字符的指针数组来存储一个字符串列表
eg:
#include <stdio.h>
const int MAX = 4;
int main ()
{
const char *names[] = {
"Zara Ali",
"Hina Ali",
"Nuha Ali",
"Sara Ali",
};
int i = 0;
for ( i = 0; i < MAX; i++)
{
printf("Value of names[%d] = %s\n", i, names[i] );
}
return 0;
}
Value of names[0] = Zara Ali
Value of names[1] = Hina Ali
Value of names[2] = Nuha Ali
Value of names[3] = Sara Ali
指针数组可用于处理具有不定数量元素的数据结构时,如动态分配的字符串数组或动态创建的结构体数组。
指向指针的指针
指向指针的指针是一种多级间接寻址的形式(即指针链)。定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
一个指向指针的指针变量必须声明,即在变量名前放置两个星号。
int **var;
当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符
eg:
#include <stdio.h>
int main ()
{
int V;
int *Pt1;
int **Pt2;
V = 100;
/* 获取 V 的地址 */
Pt1 = &V;
/* 使用运算符 & 获取 Pt1 的地址 */
Pt2 = &Pt1;
/* 使用 pptr 获取值 */
printf("var = %d\n", V );
printf("Pt1 = %p\n", Pt1 );
printf("*Pt1 = %d\n", *Pt1 );
printf("Pt2 = %p\n", Pt2 );
printf("**Pt2 = %d\n", **Pt2);
return 0;
}
var = 100 Pt1 = 0x7ffee2d5e8d8 *Pt1 = 100 Pt2 = 0x7ffee2d5e8d0 **Pt2 = 100
传递指针给函数
需声明函数参数为指针类型。
传递一个无符号的 long 型指针给函数,在函数内改变这个值
#include <stdio.h>
#include <time.h>
void getSeconds(unsigned long *par);
int main ()
{
unsigned long sec;
getSeconds( &sec );
/* 输出实际值 */
printf("Number of seconds: %ld\n", sec );
return 0;
}
void getSeconds(unsigned long *par)
{
/* 获取当前的秒数 */
*par = time( NULL );
return;
}
Number of seconds :1294450468
接受指针作为参数的函数,也能接受数组作为参数
#include <stdio.h>
/* 函数声明 */
double getAverage(int *arr, int size);
int main ()
{
/* 带有 5 个元素的整型数组 */
int balance[5] = {1000, 2, 3, 17, 50};
double avg;
/* 传递一个指向数组的指针作为参数 */
avg = getAverage( balance, 5 ) ;
/* 输出返回值 */
printf("Average value is: %f\n", avg );
return 0;
}
double getAverage(int *arr, int size)
{
int i, sum = 0;
double avg;
for (i = 0; i < size; ++i)
{
sum += arr[i];
}
avg = (double)sum / size;
return avg;
}
Average value is: 214.40000
从函数返回指针
需声明一个返回指针的函数
int * myFunction()
{
.
.
.
}
生成 10 个随机数,并使用表示指针的数组名(即第一个数组元素的地址)来返回它们
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
/* 要生成和返回随机数的函数 */
int * getRandom( )
{
static int r[10];
int i;
/* 设置种子 */
srand( (unsigned)time( NULL ) );
for ( i = 0; i < 10; ++i)
{
r[i] = rand();
printf("%d\n", r[i] );
}
return r;
}
/* 要调用上面定义函数的主函数 */
int main ()
{
/* 一个指向整数的指针 */
int *p;
int i;
p = getRandom();
for ( i = 0; i < 10; i++ )
{
printf("*(p + [%d]) : %d\n", i, *(p + i) );
}
return 0;
}
29489
16087
29989
301
8235
18976
1769
29253
5630
22679
*(p + [0]) : 29489
*(p + [1]) : 16087
*(p + [2]) : 29989
*(p + [3]) : 301
*(p + [4]) : 8235
*(p + [5]) : 18976
*(p + [6]) : 1769
*(p + [7]) : 29253
*(p + [8]) : 5630
*(p + [9]) : 22679