操作一般变量
指针的基本概念与定义
指针是C语言的核心特性之一,它是一种特殊类型的变量,用于存储内存地址。通过指针,我们可以间接访问和操作内存中的数据,这为动态内存管理、数组操作、函数调用等提供了极大的灵活性。
指针的定义与初始化:
c
// 定义整型变量
int a = 10;
// 定义指向整型的指针变量
int *p;
// 将变量a的地址赋给指针p
p = &a;
// 或者定义时直接初始化
int *q = &a;
指针操作的基本步骤
-
确定目标变量数据类型及其地址类型
- 对于变量
int a;,其数据类型为int,地址类型为int *
- 对于变量
-
基于地址类型定义指针变量
cint *pointer; // 定义int型指针 -
将目标变量地址赋值给指针
cpointer = &a; // &为取地址运算符 -
通过指针访问目标变量
c*pointer = 20; // *为解引用运算符,等价于 a = 20
指针操作的完整示例
c
#include <stdio.h>
int main() {
// 示例1:操作double类型变量
double a = 0.0;
double *p = &a; // p指向a
*p = 3.14159; // 通过指针修改a的值
printf("a = %.5f\n", a); // 输出: a = 3.14159
// 示例2:操作int类型变量
int b = 0;
int *q = &b;
*q = 100; // 等价于 b = 100
printf("b = %d\n", b); // 输出: b = 100
// 示例3:多级指针
int **r = &q; // r是指向指针的指针
**r = 200; // 等价于 *q = 200, 等价于 b = 200
printf("b = %d\n", b); // 输出: b = 200
return 0;
}
指针操作的注意事项
-
指针类型必须匹配:指针的类型必须与它指向的变量类型一致
-
空指针与野指针 :
cint *p = NULL; // 正确的空指针初始化 int *q; // 未初始化,是野指针(危险!) -
指针的大小:所有数据指针在32位系统中占4字节,在64位系统中占8字节
-
const修饰符与指针 :
cconst int *p; // 指向常量的指针,不能通过p修改指向的值 int *const p; // 常量指针,不能修改p的指向 const int *const p; // 指向常量的常量指针
操作数组的元素
指针与一维数组
数组名本身就是一个指向数组首元素的常量指针。通过指针操作数组可以提高访问效率,是C语言中常见的编程技巧。
基本操作步骤:
c
int arr[5] = {1, 2, 3, 4, 5};
// 步骤1:定义与数组元素类型匹配的指针
int *p;
// 步骤2:将数组首元素地址赋给指针
p = arr; // 等价于 p = &arr[0]
// 步骤3:通过指针访问数组元素
for(int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, *(p + i));
}
指针的算术运算
指针支持有限的算术运算,这是指针操作数组的基础:
c
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
// 指针加法
printf("*p = %d\n", *p); // 输出: 10
printf("*(p+1) = %d\n", *(p+1)); // 输出: 20
// 指针减法
int *q = &arr[4];
printf("q-p = %ld\n", q - p); // 输出: 4(两个指针之间元素的个数)
// 指针自增/自减
p++;
printf("*p = %d\n", *p); // 输出: 20
指针与二维数组
二维数组在内存中仍然是连续存储的,但指针操作稍复杂:
c
#include <stdio.h>
int main() {
int a[3][4] = {
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11}
};
// 方法1:使用指向一维数组的指针
int (*p)[4] = a; // p指向包含4个int元素的数组
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 4; j++) {
printf("a[%d][%d] = %d\t", i, j, p[i][j]);
// 等价于: *(*(p + i) + j)
}
printf("\n");
}
printf("\n");
// 方法2:将二维数组视为一维数组操作(不推荐,但可行)
int *q = &a[0][0]; // 获取第一个元素的地址
for(int i = 0; i < 12; i++) {
printf("q[%d] = %d\n", i, q[i]);
}
return 0;
}
动态数组与指针
c
#include <stdio.h>
#include <stdlib.h>
int main() {
int n;
printf("请输入数组大小: ");
scanf("%d", &n);
// 动态分配内存
int *arr = (int*)malloc(n * sizeof(int));
if(arr == NULL) {
printf("内存分配失败!\n");
return 1;
}
// 使用指针初始化数组
for(int i = 0; i < n; i++) {
arr[i] = i * 10; // 等价于 *(arr + i) = i * 10
}
// 输出数组元素
for(int i = 0; i < n; i++) {
printf("arr[%d] = %d\n", i, *(arr + i));
}
// 释放内存
free(arr);
arr = NULL; // 避免悬空指针
return 0;
}
操作函数(函数指针)
函数指针的基本概念
函数指针是指向函数的指针变量,它存储的是函数代码的起始地址。通过函数指针,我们可以动态调用不同的函数,实现回调机制等高级功能。
函数指针的定义步骤:
-
确定函数类型:函数类型由返回值类型和参数类型共同决定
c// 原函数声明 int add(int x, int y); // 函数类型:int (int, int) -
定义函数指针 :在函数类型基础上添加
(*指针名)cint (*func_ptr)(int, int); // 定义函数指针 -
初始化函数指针:将函数地址赋给指针
cfunc_ptr = add; // 直接使用函数名 // 或 func_ptr = &add; // 使用取地址运算符
函数指针的使用示例
c
#include <stdio.h>
// 函数声明
int add(int x, int y);
int subtract(int x, int y);
int multiply(int x, int y);
int divide(int x, int y);
int main() {
// 定义函数指针
int (*operation)(int, int);
int a = 10, b = 5, result;
// 使用函数指针调用add函数
operation = add;
result = operation(a, b);
printf("%d + %d = %d\n", a, b, result);
// 使用函数指针调用subtract函数
operation = subtract;
result = operation(a, b);
printf("%d - %d = %d\n", a, b, result);
// 直接调用和通过指针调用是等价的
result = (*operation)(a, b); // 传统方式
result = operation(a, b); // 简化方式(推荐)
return 0;
}
// 函数定义
int add(int x, int y) {
return x + y;
}
int subtract(int x, int y) {
return x - y;
}
int multiply(int x, int y) {
return x * y;
}
int divide(int x, int y) {
if(y != 0) {
return x / y;
}
return 0;
}
函数指针数组
函数指针可以存储在数组中,这在实现状态机、命令模式等场景中非常有用:
c
#include <stdio.h>
// 定义几个测试函数
void func1() { printf("执行函数1\n"); }
void func2() { printf("执行函数2\n"); }
void func3() { printf("执行函数3\n"); }
int main() {
// 定义函数指针数组
void (*func_array[3])() = {func1, func2, func3};
// 通过数组索引调用不同的函数
for(int i = 0; i < 3; i++) {
printf("调用func_array[%d]: ", i);
func_array[i]();
}
return 0;
}
函数指针详解
函数指针的定义与typedef
为了提高代码可读性,通常使用typedef为函数指针类型创建别名:
c
#include <stdio.h>
// 使用typedef定义函数指针类型
typedef int (*MathFunc)(int, int);
// 函数声明
int add(int x, int y);
int max(int x, int y);
int main() {
// 使用类型别名定义函数指针
MathFunc func_ptr;
func_ptr = add;
printf("10 + 5 = %d\n", func_ptr(10, 5));
func_ptr = max;
printf("max(10, 5) = %d\n", func_ptr(10, 5));
return 0;
}
int add(int x, int y) {
return x + y;
}
int max(int x, int y) {
return (x > y) ? x : y;
}
复杂函数指针类型
c
#include <stdio.h>
// 函数指针作为参数
typedef void (*Callback)(int, void*);
// 函数指针作为返回值
typedef Callback (*GetCallback)(void);
// 回调函数示例
void progress_callback(int progress, void* user_data) {
printf("进度: %d%%, 用户数据: %p\n", progress, user_data);
}
// 返回回调函数的函数
Callback get_default_callback() {
return progress_callback;
}
int main() {
// 获取回调函数
GetCallback getter = get_default_callback;
Callback cb = getter();
// 使用回调函数
int user_data = 12345;
cb(50, &user_data);
return 0;
}
函数指针与qsort函数
函数指针在标准库函数中广泛应用,qsort就是一个典型例子:
c
#include <stdio.h>
#include <stdlib.h>
// 比较函数:用于qsort
int compare_int(const void* a, const void* b) {
return (*(int*)a - *(int*)b);
}
int compare_string(const void* a, const void* b) {
return strcmp(*(const char**)a, *(const char**)b);
}
int main() {
// 整数数组排序
int int_array[] = {9, 5, 7, 1, 3, 8, 2, 6, 4};
int int_count = sizeof(int_array) / sizeof(int_array[0]);
qsort(int_array, int_count, sizeof(int), compare_int);
printf("排序后的整数数组: ");
for(int i = 0; i < int_count; i++) {
printf("%d ", int_array[i]);
}
printf("\n");
// 字符串数组排序
const char* str_array[] = {"banana", "apple", "orange", "grape", "cherry"};
int str_count = sizeof(str_array) / sizeof(str_array[0]);
qsort(str_array, str_count, sizeof(char*), compare_string);
printf("排序后的字符串数组: ");
for(int i = 0; i < str_count; i++) {
printf("%s ", str_array[i]);
}
printf("\n");
return 0;
}
如何用函数指针调用函数
函数指针调用的多种形式
c
#include <stdio.h>
int multiply(int x, int y) {
return x * y;
}
int main() {
int (*func_ptr)(int, int);
// 赋值方式1:使用函数名
func_ptr = multiply;
// 赋值方式2:使用取地址运算符
func_ptr = &multiply;
int a = 5, b = 6;
// 调用方式1:像普通函数一样调用(推荐)
int result1 = func_ptr(a, b);
// 调用方式2:使用解引用运算符(传统方式)
int result2 = (*func_ptr)(a, b);
// 调用方式3:使用双重解引用(合法但不常用)
int result3 = (*(*func_ptr))(a, b);
printf("结果1: %d\n", result1);
printf("结果2: %d\n", result2);
printf("结果3: %d\n", result3);
return 0;
}
函数指针作为函数参数(回调函数)
回调函数的基本模式
回调函数是一种强大的编程模式,允许函数将其部分逻辑交给调用者定义:
c
#include <stdio.h>
#include <math.h>
// 定义回调函数类型
typedef double (*TransformFunc)(double);
// 接受回调函数作为参数的函数
void transform_array(double arr[], int size, TransformFunc func) {
for(int i = 0; i < size; i++) {
arr[i] = func(arr[i]);
}
}
// 各种变换函数
double square(double x) { return x * x; }
double cube(double x) { return x * x * x; }
double square_root(double x) { return sqrt(x); }
double reciprocal(double x) { return 1.0 / x; }
int main() {
double numbers[] = {1.0, 2.0, 3.0, 4.0, 5.0};
int count = sizeof(numbers) / sizeof(numbers[0]);
printf("原始数组: ");
for(int i = 0; i < count; i++) {
printf("%.2f ", numbers[i]);
}
printf("\n");
// 使用不同的回调函数
transform_array(numbers, count, square);
printf("平方后: ");
for(int i = 0; i < count; i++) {
printf("%.2f ", numbers[i]);
}
printf("\n");
// 重置数组
double numbers2[] = {1.0, 2.0, 3.0, 4.0, 5.0};
transform_array(numbers2, count, square_root);
printf("开方后: ");
for(int i = 0; i < count; i++) {
printf("%.2f ", numbers2[i]);
}
printf("\n");
return 0;
}
带上下文信息的回调函数
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 回调函数类型,带用户数据参数
typedef void (*DataCallback)(const char* data, void* user_data);
// 数据处理函数
void process_data(const char* input, DataCallback callback, void* user_data) {
printf("处理数据: %s\n", input);
// 模拟数据处理
char* processed = malloc(strlen(input) + 10);
sprintf(processed, "已处理: %s", input);
// 调用回调函数
callback(processed, user_data);
free(processed);
}
// 回调函数1:打印数据
void print_callback(const char* data, void* user_data) {
printf("打印回调: %s (用户数据: %d)\n", data, *(int*)user_data);
}
// 回调函数2:保存数据到文件
void save_callback(const char* data, void* user_data) {
FILE* file = (FILE*)user_data;
fprintf(file, "%s\n", data);
printf("数据已保存到文件\n");
}
int main() {
// 示例1:使用打印回调
int user_value = 42;
process_data("测试数据", print_callback, &user_value);
// 示例2:使用保存回调
FILE* file = fopen("output.txt", "w");
if(file) {
process_data("需要保存的数据", save_callback, file);
fclose(file);
}
return 0;
}
指针练习 - 字符串函数实现
字符串长度函数
c
#include <stdio.h>
// 计算字符串长度(标准实现)
size_t xyd_strlen(const char* str) {
const char* p = str;
while(*p != '\0') {
p++;
}
return p - str; // 指针相减得到长度
}
// 计算字符串长度(带安全检查)
size_t xyd_strlen_safe(const char* str, size_t max_len) {
const char* p = str;
size_t len = 0;
while(len < max_len && *p != '\0') {
p++;
len++;
}
return len;
}
int main() {
char str[] = "Hello, World!";
printf("字符串: %s\n", str);
printf("长度: %zu\n", xyd_strlen(str));
printf("安全长度(限制5): %zu\n", xyd_strlen_safe(str, 5));
return 0;
}
字符串拷贝函数
c
#include <stdio.h>
// 字符串拷贝
char* xyd_strcpy(char* dest, const char* src) {
if(dest == NULL || src == NULL) {
return NULL;
}
char* d = dest;
const char* s = src;
while((*d++ = *s++) != '\0') {
// 空循环体,所有操作都在条件中完成
}
return dest;
}
// 带长度限制的字符串拷贝
char* xyd_strncpy(char* dest, const char* src, size_t n) {
if(dest == NULL || src == NULL || n == 0) {
return dest;
}
char* d = dest;
const char* s = src;
size_t i;
// 拷贝最多n-1个字符
for(i = 0; i < n - 1 && *s != '\0'; i++) {
*d++ = *s++;
}
// 确保目标字符串以'\0'结尾
*d = '\0';
return dest;
}
int main() {
char dest1[20];
char dest2[20];
const char* src = "Hello, World!";
xyd_strcpy(dest1, src);
printf("strcpy: %s\n", dest1);
xyd_strncpy(dest2, src, 5);
printf("strncpy(5): %s\n", dest2);
return 0;
}
字符串拼接函数
c
#include <stdio.h>
// 字符串拼接
char* xyd_strcat(char* dest, const char* src) {
if(dest == NULL || src == NULL) {
return dest;
}
char* d = dest;
// 找到dest的结尾
while(*d != '\0') {
d++;
}
// 追加src
while((*d++ = *src++) != '\0') {
// 空循环体
}
return dest;
}
// 带长度检查的字符串拼接
char* xyd_strncat(char* dest, const char* src, size_t n) {
if(dest == NULL || src == NULL || n == 0) {
return dest;
}
char* d = dest;
// 找到dest的结尾
while(*d != '\0') {
d++;
}
// 追加最多n个字符
size_t i;
for(i = 0; i < n && *src != '\0'; i++) {
*d++ = *src++;
}
// 添加终止符
*d = '\0';
return dest;
}
int main() {
char str1[50] = "Hello, ";
char str2[50] = "Hello, ";
xyd_strcat(str1, "World!");
printf("strcat: %s\n", str1);
xyd_strncat(str2, "World!", 3);
printf("strncat(3): %s\n", str2);
return 0;
}
字符串比较函数
c
#include <stdio.h>
// 字符串比较
int xyd_strcmp(const char* str1, const char* str2) {
while(*str1 && *str2 && *str1 == *str2) {
str1++;
str2++;
}
return *(unsigned char*)str1 - *(unsigned char*)str2;
}
// 不区分大小写的字符串比较
int xyd_stricmp(const char* str1, const char* str2) {
while(*str1 && *str2) {
char c1 = *str1;
char c2 = *str2;
// 转换为小写比较
if(c1 >= 'A' && c1 <= 'Z') c1 += 32;
if(c2 >= 'A' && c2 <= 'Z') c2 += 32;
if(c1 != c2) {
break;
}
str1++;
str2++;
}
char c1 = *str1;
char c2 = *str2;
if(c1 >= 'A' && c1 <= 'Z') c1 += 32;
if(c2 >= 'A' && c2 <= 'Z') c2 += 32;
return c1 - c2;
}
// 带长度限制的字符串比较
int xyd_strncmp(const char* str1, const char* str2, size_t n) {
if(n == 0) {
return 0;
}
while(--n && *str1 && *str2 && *str1 == *str2) {
str1++;
str2++;
}
return *(unsigned char*)str1 - *(unsigned char*)str2;
}
int main() {
const char* str1 = "Hello";
const char* str2 = "hello";
const char* str3 = "Hello, World!";
printf("strcmp(\"%s\", \"%s\") = %d\n", str1, str2, xyd_strcmp(str1, str2));
printf("stricmp(\"%s\", \"%s\") = %d\n", str1, str2, xyd_stricmp(str1, str2));
printf("strncmp(\"%s\", \"%s\", 3) = %d\n", str1, str3, xyd_strncmp(str1, str3, 3));
return 0;
}
编程建议
-
始终初始化指针:
cint *p = NULL; // 好的实践 int *q; // 坏的实践 -
检查指针有效性:
cif(pointer != NULL) { // 使用指针 } -
避免悬空指针:
cfree(ptr); ptr = NULL; // 释放后立即置空 -
使用const修饰符:
cconst char* str = "Hello"; // 不能通过str修改字符串内容