跳到主要内容

指针基础

函数(续)

exit 函数

  • return 关键字:返回当前函数调用,将返回值返回调用者。(在底层,会调用 _exit() 函数。)
  • exit() 函数:退出当前程序。
// 函数声明
// int test(int a, char ch);
int test(int, char); // 函数声明的简化写法。声明时,形参名 可以省略。
int main(void)
{
// 函数调用
int ret = test(10, 'a'); // test函数,调用结束,return 给 main
printf("test函数返回:ret = %d\n", ret);
// return 0; // 返回给调用者(启动例程)—— 作用:结束程序。
exit(0); // 结束程序。
}
// 函数定义
int test(int a, char ch)
{
printf("a = %d\n", a);
printf("ch = %c\n", ch);
// return 97; // 返回给调用者,程序不结束。
// 结束程序
exit(97); // 使用 #include <stdlib.h>
}

多文件编程

头文件守卫:为了防止头文件被重复包含(如 head.h)。

  1. #pragma once 是 VS 自动生成的,只应用于 windows 系统。
  2. 使用 #ifndef / #define / #endif(推荐,跨平台)。
#ifndef _HEAD_H_ // 习惯写成这样。
#define _HEAD_H_
// ...头文件内容:#include <xx.h> / 宏定义 #define PI 3.14 / 函数声明 / 类型定义
#endif
// 示例:
#ifndef _HEAD_H_ // 标准引入"头文件守卫"
#define _HEAD_H_
// include 头文件
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
// 函数声明
int add(int a, int b);
int sub(int a, int b);
// 宏定义
#define PI 3.14
// 类型定义
#endif
// main函数所在的 .c 文件中:
#include "head.h"
  • <> 包裹的是 系统库头文件。
  • "" 包裹的是 用户自定义头文件。

指针

指针和内存单元

  • 指针:地址!
  • 指针变量:用来存储地址的变量!
  • 内存单元:是计算机中内存最小的存储单位。内存单元大小 —— 1 字节(8 bit 位)。
  • 每个内存单元,都有一个唯一的编号。这个内存单元的编号,称之为"地址"。

指针的定义和使用

*p 作用:将 p 变量的内容取出,当成地址看待,找到该地址对应的内存空间。

  • 如果做左值,存储数据到空间中。
  • 如果做右值,取出空间中的内容。
int a = 10;
int *p = &a; // int* p --- windows int *p --- Linux int * p 依据个人习惯
// int *p = &a; 展开:
// int *p;
// p = &a;
a = 999;
printf("*p = %d\n", *p); // 间接引用 —— 右值。
// 解引用、间接引用。
*p = 250; // 间接引用 —— 左值。
printf("a = %d\n", a);

指针类型大小(指针,算一种自定义数据类型,如 int *):

  • 指针的大小,与类型无关!只与当前使用的平台架构有关(32位:4字节、64位:8字节)。
  • 可以在一条语句中,同时定义多个指针变量、普通变量。
int main(void)
{
printf("int *的大小:%u\n", sizeof(int*));
printf("short *的大小:%u\n", sizeof(short*));
printf("char *的大小:%u\n", sizeof(char*));
printf("long *的大小:%u\n", sizeof(long*));
printf("long long *的大小:%u\n", sizeof(long long *));
}
int a, b, c; // 多个普通变量
int *p1, *p2, *p3; // 多个指针变量。每个变量都有一个自己的 *
int a, *b, *c, d; // 定义整型变量 a、d,同时定义指针变量 b、c

空指针和野指针

野指针

  1. 没有使用"有效"的地址,给指针初始化。
  2. p 指针变量有值,但是该值不是一个有效的地址。

【结论】:编程时,杜绝野指针。

int* p; // 没有给 p 指定一个有效地址。
*p = 1000;
int* p = 10; // 没有给 p 指定一个有效地址。
*p = 1000;

空指针

NULL == 0 == \0

int* p = NULL; // #define NULL ((void *)0) // 定义一个空指针。
*p = 1000; // *p 所指向的内存空间,是一个"无效访问区域"。

泛型指针(万能指针 void *)

可以接受任意一种变量的地址。但是,在使用时【必须】借助"强制类型转换"具体化数据类型。

void * 类型的大小:32位:4字节;64位:8字节。

int main(void)
{
printf("void *的大小:%u\n", sizeof(void *));
char ch = 'R';
void* p; // 泛型指针(万能指针)
p = &ch;
printf("*p = %c", *(char *)p); // 将 p 变量类型,由 void* 强转成 char *
return EXIT_SUCCESS;
}

const 关键字

修饰变量

const int a = 20; // a 为只读变量,不能修改。
//a = 200; // 不可以修改
int* p = &a;
*p = 677;
printf("a = %d\n", a); // 借助指针可以修改 const 普通变量的值。

修饰指针

常用:在函数内,限制指针所指向的内存空间为只读(不允许修改)。

// 方式1
const int *p; // 向后(右)作用
// 示例:
int a = 10;
int b = 20;
const int *p = &a;
*p = 500; //【失败】:将 a 的值改为 500,不能改!
p = &b; //【成功】:可以修改 p 变量的内容(地址)。

// 方式2
int const *p; // 向后(右)作用
// 作用方式同上!

指针和数组

数组名

数组名,是地址常量。—— 不可以被修改、赋值(=+=-=*=/=%= 等带有副作用的运算符)。

指针,是变量,可以使用数组名给指针赋值。

int a[3] = { 1,2,3 }; // a 就是数组地址。
int b[3]; // b 是常量
b = a; // 不允许!! 因为 b 为地址常量。
int *p = a; // a 就是数组地址。允许!!

取数组元素

int arr[] = {1, 3, 5, 7, 9};
int *p = arr; // 使用数组地址,给 p 指针变量初始化
// 结论:
arr[0] == *(arr+0) == p[0] == *(p+0)
arr[1] == *(arr+1) == p[1] == *(p+1)
arr[2] == *(arr+2) == p[2] == *(p+2)
// ...
arr[N] == *(arr+N) == p[N] == *(p+N)

指针和数组名区别

  1. 指针是变量,数组名是常量。
  2. sizeof(指针) ——> 4字节、8字节。
  3. sizeof(数组名) ——> 数组实际的字节数。

指针的算术运算

数据类型对指针的作用

  1. 间接引用(解引用):指针的数据类型,决定了从指针存储的地址开始,向后读取的字节数(与指针本身存储空间无关)。
int a = 0x12345678;
//int* p = &a;
//int* p; --- 0x12345678;
//short* p; --- 0x5678;
char* p; // --- 0x78;
p = &a;
printf("%#x\n", *p);
  1. 加减运算:指针的数据类型,决定了指针进行 +/- 操作时,向后/前跳过的字节数。
int * +1 实际加过 4 字节。
short * +1 实际加过 2 字节。
char * +1 实际加过 1 字节。
long long * +1 实际加过 8 字节。

指针 ++ 操作数组

练习:使用指针给空数组连续赋值,再使用"指针挪移"方法打印出这个数组。

int main(void)
{
int arr[10];
int n = sizeof(arr) / sizeof(arr[0]);
int* p = arr; // p 指向 arr[0]
printf("p = %p\n", p);
// 使用 指针,给空数组连续赋值。
for (size_t i = 0; i < n; i++)
{
//arr[i] = i + 10;
*(p + i) = 10 + i; // *(p + i) == arr[i];
}
// 循环结束时,p 指向谁??? 依然指向 arr[0];
printf("循环结束后:p = %p\n", p);
// 使用"指针挪移"方法打印出这个数组。
for (size_t i = 0; i < n; i++)
{
printf("%d ", *p);
p++; // p = p+1, 一次加过 一个 int 大小(一个数组元素)。
}
printf("\n");
// 循环结束时,p 指向谁??? 指向数组尾元素的下一个内存(野指针)。
printf("2 for 循环结束后:p = %p\n", p);
return 0;
}

另一种写法(已有初值的数组):

int arr[] = {1,2,3,4,5,6,7,8,9,0};
int* p = arr;
int n = sizeof(arr) / sizeof(arr[0]);
//for (size_t i = 0; i < n; i++)
//{
// printf("%d ", arr[i]);
//}
for (size_t i = 0; i < n; i++)
{
printf("%d ", *p);
p++; // p = p+1, 一次加过 一个 int 大小(一个数组元素)。
}
// p 值随着循环,不断变化,打印结束后,p 指向一块无效的内存空间(野指针)
printf("\n");
return 0;
}

指针 +- 整数

  1. 普通指针变量 +- 整数。
  2. 在数组中,+- 整数。
  3. &数组名 + 1:加过的是一个数组的总大小。
char *p; p+1 偏过1个字节。
short *p; p+1 偏过2个字节。
int *p; p+1 偏过4个字节。
short arr[] = {1, 3, 5, 8, 12, 17, 19};
short *p = arr+3;
p + 3; // 向后(右)偏过 3 个元素。 6 个字节。
p - 2; // 向前(左)偏过 2 个元素。 4 个字节。

指针其他运算

  • 指针 * / %:不允许!!!
  • 指针 + 指针:不允许 error!
  • 指针 - 指针:
    • 对普通变量来说,语法允许,但无实际意义。
    • 对于数组来说,得到偏移过的元素个数。

指针实现 strlen()

借助 数组 实现:

// 数组实现
int myStrlen1(char str[])
{
int i = 0;
while (str[i] != '\0')
{
i++;
}
return i;
}

借助 指针 实现:

// 指针实现
int myStrlen2(char str[])
{
char* p = str;
while (*p != '\0')
{
p++;
}
return p-str; // 返回元素个数。
}

指针比较运算

  1. 普通变量:语法允许,但无实际意义。
  2. 数组:对于数组来说,地址之间可以比大小,得到元素存储的先后顺序。
int main(void)
{
int a[] = {1,2,3,4,5,6,7,8,9,0};
int* p = a; // 保存数组首地址。
p = &a[3]; // 修改 p 保存的地址
printf("p-a = %d\n", p - a); // ---- 3
int* q = &a[8];
printf("q-p = %d\n", q - p); // ----- 5
return 0;
}
int arr[] = {1,2,3,5,6,7,8};
int* p = &arr[2];
if (p > arr) {
printf("成立\n");
}
else if (p < arr) {
printf("不成立");
}
else {
printf("==\n");
}
  1. 判断 NULL:
int* p;
int a = 10;
// p = NULL // 初始化空指针。
p = &a;
if (p != NULL)
{
printf("p is not NULL\n");
}
else
{
printf("p is NULL\n");
}

指针数组

  1. 指针数组的本质,是一个二级指针。
  2. 二维数组,是指针数组,是二级指针。
int a = 10;
int b = 20;
int c = 30;
int *arr[] = {&a, &b, &c}; // int型指针数组,保存地址的数组。
printf("*arr[0] = %d\n", *arr[0]); // arr[0] == *(arr+0)
printf("*arr[0] = %d\n", *(*(arr + 0)));
printf("*arr[0] = %d\n", **arr); // * 结合性,自右向左。
int a[] = { 10 };
int b[] = { 20 };
int c[] = { 30 };
int* arr[] = {a, b, c}; // 存地址
printf("arr[0][0] = %d\n", arr[0][0]);
printf("*(*(arr+0)) = %d\n", *(*(arr+0)));
printf("**arr = %d\n", **arr); // 二级指针的间接引用。

多级指针

📷(此处原文为配图,略)

(本节内容在下一章"08-高级语法(多级指针、指针和字符串)"继续展开。)