目录
前言
这篇博客主要介绍C语言中的结构体和指针的用法。
1.指针
指针是C语言中的一种重要概念,它存储了一个变量的内存地址。通过指针,可以直接访问内存中的数据,实现对数据的直接操作。
1.声明
指针的声明告诉编译器变量将存储一个地址。在声明指针时,需要指定指针所指向变量的类型。
例如在下面的例子中我们声明了一个指向整数的指针变量。
int * ptr;
2. 取地址运算符
&运算符返回其后操作数的地址。
例如在下面的代码中,我们声明了一个指针变量,使用&我们可以获取指针指向的内存地址。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[]) {
int * ptr;
printf("指针地址:%p\n",&ptr);
return 0;
}
3. 间接访问运算符
* 运算符用于访问指针指向的内存中的值。
在下面的例子中,我们声明了一个指针变量,指向变量sum的地址,我们使用*ptr取出指针指向内存空间的值。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[]) {
int sum = 100;
int * ptr = ∑
printf("指针地址:%p\n",&ptr);
printf("指针变量的值:%d\n",*ptr);
return 0;
}
4. 指针的初始化
指针可以被初始化为某个变量的地址,也可以初始化为 `NULL`,表示指针不指向任何有效的内存地址。
在上面的例子中,我们使用指针变量直接指向一个已经存在的变量。
在下面的例子中,我们直接初始化指针变量为NUL。
int * p = NULL;
5. 指针的算术运算
在C语言中,指针算术运算允许对指针进行加法、减法运算。这些运算通常用于访问数组的不同元素或者在动态内存分配中移动指针。
指针算术运算的规则如下:
1. 加法运算:对指针进行加法运算时,指针的值会增加,增加的量取决于指针所指向类型的大小。例如,ptr + 1将使指针指向下一个元素的地址。
2. 减法运算:对指针进行减法运算时,指针的值会减少,减少的量同样取决于指针所指向类型的大小。例如,ptr - 1 将使指针指向前一个元素的地址。
3. 指针之间的减法运算:两个指针相减的结果是两个指针之间的元素个数。例如,(ptr2 - ptr1)将返回 ptr2指向的元素在内存中相对于 ptr1指向的元素的偏移量。
以下是一个简单的示例,演示了指针算术运算的基本用法:
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr; // 指向数组第一个元素的指针
// 使用指针算术运算访问数组元素
printf("第一个元素:%d\n", *ptr); // 打印第一个元素
printf("第二个元素:%d\n", *(ptr + 1)); // 打印第二个元素
printf("第三个元素:%d\n", *(ptr + 2)); // 打印第三个元素
// 使用指针之间的减法运算计算数组元素个数
int *ptr2 = &arr[4];
printf("数组元素个数:%ld\n", ptr2 - ptr + 1); // 打印数组元素个数
return 0;
}
这个示例中,我们定义了一个整型数组 arr,然后声明了一个指向数组第一个元素的指针 `ptr`。通过指针算术运算,我们访问了数组的不同元素,并且计算了数组的元素个数。
6. 指针和数组
指针和数组在C语言中密切相关,并且有许多相似的地方。实际上,在C语言中,数组名本身就是一个指向数组第一个元素的指针。
1. 数组名作为指针
在大多数情况下,数组名被隐式地转换为指向数组第一个元素的指针。
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr; // 指向数组第一个元素的指针
2. 指针和数组的关系
指针和数组之间可以互相转换。指向数组第一个元素的指针可以用于访问数组的所有元素,并且可以对指针进行算术运算以访问不同的数组元素。
例如在下面的例子中,我们可以通过对指针的操作来访问数组中的数据元素。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[]) {
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr; // 指向数组第一个元素的指针
for (int i = 0; i < 5; i++) {
printf("%d\t",*(ptr+i));
}
printf("\n");
return 0;
}
3. 指针和数组的区别
虽然数组名可以像指针一样使用,但数组名不是可修改的左值,而指针是可修改的左值。这意味着您不能对数组名进行赋值操作,但可以对指针进行赋值操作。
4. 数组作为函数参数
当将数组作为函数参数传递时,实际上传递的是数组的地址(即数组的第一个元素的地址)。因此,在函数内部可以通过指针来访问数组的元素。
示例代码如下,在这个示例中,我们定义了一个整型数组 arr,然后声明了一个指向数组第一个元素的指针 ptr。通过指针和数组名,我们访问了数组的所有元素,并且演示了将数组作为函数参数传递的过程。
#include <stdio.h>
#include <stdlib.h>
//打印数组中的数据元素
void printArray(int * ptr,int size){
for (int i = 0; i < size; i++) {
printf("%d ", *(ptr + i)); // 使用指针访问数组元素
}
printf("\n");
}
int main(int argc, const char *argv[]) {
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr; // 指向数组第一个元素的指针
printArray(arr,5);
return 0;
}
当们使用数组名作为函数参数传递的时候,其实传递的是引用。例如在下面的例子中,我们在函数中修改数组,原来的数组已经被修改了。
#include <stdio.h>
#include <stdlib.h>
// 定义一个函数,该函数接受一个整型数组和数组的长度作为参数,并将数组中的元素值加倍
void doubleArray(int *arr, int length) {
for (int i = 0; i < length; i++) {
arr[i] *= 2; // 修改数组元素的值
}
}
int main(int argc, const char *argv[]) {
int arr[] = {1, 2, 3, 4, 5};
int length = sizeof(arr) / sizeof(arr[0]);
// 调用函数,传递数组的地址和数组的长度
doubleArray(arr, length);
// 打印修改后的数组
printf("修改后的数组:\n");
for (int i = 0; i < length; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
7. 指针和函数
在C语言中,函数和指针之间有着密切的关系,使用指针可以实现一些高级的函数功能,如传递函数、返回函数指针等。以下是一些关于函数和指针的重要概念:
-
函数指针:函数指针是指向函数的指针变量。与指向变量的指针类似,函数指针存储函数的地址,使得可以通过该指针调用相应的函数。
-
函数指针的声明:函数指针的声明方式与函数原型类似,只是需要在参数列表和函数名之间添加
(*ptr)
。例如,int (*ptr)(int, int);
声明了一个函数指针ptr
,该函数接受两个整数参数并返回整数。 -
使用函数指针:可以通过函数指针调用相应的函数,使用方式与直接调用函数相同,只需将函数指针名称后面加上括号并传递参数即可。例如,
result = (*ptr)(a, b);
调用了函数指针ptr
所指向的函数,并传递了参数a
和b
。 -
函数指针作为参数:函数指针可以作为函数的参数传递给其他函数,使得函数能够调用不同的函数。这种技术被称为“回调函数”。
-
函数指针作为返回值:函数指针还可以作为函数的返回值返回,这在一些情况下非常有用。
下面的例子中,说明了使用使用函数指针。
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
// 定义一个函数,该函数接受两个整数参数并返回它们的和
int add(int a, int b) {
return a + b;
}
// 定义一个函数,该函数接受两个整数参数并返回它们的差
int subtract(int a, int b) {
return a - b;
}
// 函数指针作为参数的函数
int operation(int x, int y, int (*ptr)(int, int)) {
return (*ptr)(x, y);
}
int main(int argc, const char *argv[]) {
int result;
// 声明一个函数指针并初始化为指向 add 函数的地址
int (*ptr)(int, int) = add;
// 使用函数指针调用 add 函数
result = (*ptr)(4, 3);
printf("4 + 3 = %d\n", result);
// 使用函数指针调用 subtract 函数
ptr = subtract;
result = (*ptr)(4, 3);
printf("4 - 3 = %d\n", result);
// 函数指针作为参数传递给 operation 函数
result = operation(4, 3, add);
printf("4 + 3 = %d\n", result);
return 0;
}
8. 指针和动态内存分配
在C语言中,动态内存分配允许程序在运行时请求内存空间,这样就可以根据程序的需要动态地分配和释放内存。指针在动态内存分配中起着重要的作用,因为它们用于跟踪分配的内存块的地址,从而允许程序访问和操作这些内存块。
C语言中动态内存分配主要依赖于两个函数:malloc() 和 free()。
1. malloc() 函数
malloc() 函数用于动态分配指定大小的内存块,并返回一个指向该内存块的指针。如果分配成功,则返回指向分配内存的指针;如果分配失败,则返回 NULL。
例如在下面的代码中,分配了足够存储 10 个整数的内存空间,并将分配的内存地址存储在指针 ptr中。
int *ptr = (int *)malloc(10 * sizeof(int));
2. free() 函数
free()函数用于释放之前通过 malloc()函数分配的内存块。一旦释放了内存块,该内存块就可以重新分配给其他用途。
下面的demo实例中,演示了如何使用 malloc()和 free() 函数进行动态内存分配和释放。
在这个示例中,我们首先通过 malloc() 函数动态分配了足够存储 n个整数的内存空间,并将分配的内存地址存储在指针 ptr中。然后,我们从用户输入获取了 n个整数,并将它们存储到分配的内存中。最后,我们打印了用户输入的整数,并使用 free()函数释放了先前分配的内存空间。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[]) {
int *ptr;
int n;
printf("输入要分配的整数个数:");
scanf("%d", &n);
// 分配内存空间
ptr = (int *)malloc(n * sizeof(int));
if (ptr == NULL) {
printf("内存分配失败\n");
return 1;
}
// 从用户输入获取整数并存储到分配的内存中
printf("输入 %d 个整数:\n", n);
for (int i = 0; i < n; i++) {
scanf("%d", &ptr[i]);
}
// 打印用户输入的整数
printf("输入的整数为:");
for (int i = 0; i < n; i++) {
printf("%d ", ptr[i]);
}
printf("\n");
// 释放内存空间
free(ptr);
return 0;
}
2.结构体
在C语言中,结构体是一种用户自定义的数据类型,用于将多个不同类型的数据组合在一起。结构体允许程序员将相关的数据组织在一起,以便更方便地管理和操作这些数据。
1.定义
C语言中结构体使用struct关键字定义,其语法格式如下:
例如在下面的代码中,我们定义了一个Person结构体。
// 定义一个结构体
struct Person {
char name[50];
int age;
float height;
};
2.结构体成员访问
在定义结构体之后,可以使用该结构体名称创建结构体变量,并访问结构体成员。以下是一个简单的示例:
当结构体的成员是指针类型时,访问这些成员需要使用间接访问的方式。这通常涉及到使用箭头操作符 ->
来访问结构体成员指向的数据。
例如在下面的代码中,结构体中还有指针变量,我们通过“->”访问结构体中的数据成员。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义一个结构体,其中包含指针成员
struct Student {
char *name;
int *scores;
};
// 初始化结构体变量的函数
void initializeStudent(struct Student *student, const char *name, int numScores) {
// 为 name 成员分配内存并复制字符串
student->name = (char *)malloc(strlen(name) + 1);
strcpy(student->name, name);
// 为 scores 成员分配内存并初始化成绩
student->scores = (int *)malloc(numScores * sizeof(int));
for (int i = 0; i < numScores; i++) {
student->scores[i] = 0;
}
}
// 释放结构体变量占用的内存的函数
void freeStudent(struct Student *student) {
free(student->name);
free(student->scores);
}
// 打印结构体变量的函数
void printStudent(struct Student *student, int numScores) {
printf("姓名:%s\n", student->name);
printf("成绩:");
for (int i = 0; i < numScores; i++) {
printf("%d ", student->scores[i]);
}
printf("\n");
}
int main() {
// 创建结构体变量
struct Student student1;
// 初始化结构体变量
initializeStudent(&student1, "Alice", 3);
// 修改结构体成员的值
student1.scores[0] = 90;
student1.scores[1] = 85;
student1.scores[2] = 95;
// 打印结构体变量的值
printStudent(&student1, 3);
// 释放结构体变量占用的内存
freeStudent(&student1);
return 0;
}
3.给结构体起别名
在C语言中,可以使用 typedef
关键字给结构体起别名。typedef
关键字用于定义类型的别名,通过给结构体起别名,可以简化代码并提高可读性。以下是一个示例:
#include <stdio.h>
// 定义一个结构体
struct Student {
char name[50];
int age;
};
// 给结构体起别名
typedef struct Student Stu;
int main() {
// 使用别名创建结构体变量
Stu student1;
// 赋值给结构体成员
strcpy(student1.name, "Alice");
student1.age = 25;
// 打印结构体成员的值
printf("姓名:%s\n", student1.name);
printf("年龄:%d\n", student1.age);
return 0;
}
在这个示例中,我们定义了一个结构体 Student
,包含了 name
和 age
两个成员。然后,使用 typedef
关键字为结构体 Student
起了别名 Stu
。在 main
函数中,我们使用别名 Stu
创建了一个结构体变量 student1
,并赋值给结构体成员。最后,我们打印了结构体成员的值。
通过给结构体起别名,我们可以使用更简洁的方式来创建结构体变量,并提高代码的可读性。