第八天: C语言深度探索:指针与函数的奥秘

1 字符指针与字符串处理

字符指针与数组的关系

在C语言中,字符串可以被视为字符数组。数组名可以看作是指向其第一个元素的指针。因此,我们可以使用相同类型的指针来访问数组中的每个元素。这种对应关系使得字符指针在处理字符串时显得特别方便。

c

#include <stdio.h>int main() {// 定义一个字符数组char strArray[] = "hello world";// 使用数组名作为指针访问字符串printf("%s\n", strArray); // 输出 "hello world"// 使用字符指针访问字符串char *ptr = strArray;printf("%s\n", ptr); // 输出 "hello world"return 0;
}

字符串的表示

我们可以直接使用双引号来定义一个字符串,例如 "hello world"。这里需要注意的是,字符串的末尾需要补上一个空字符('\0'),表示字符串的结束。

c

#include <stdio.h>int main() {// 定义一个字符串char str[] = "hello world"; // 隐含地在末尾添加 '\0'// 输出字符串长度(包括 '\0')printf("Length: %lu\n", sizeof(str)); // 输出 12// 检查字符串末尾的 '\0'printf("%c\n", *(str + 11)); // 输出 '\0' 或不输出(取决于你的环境)return 0;
}

字符数组与字符指针

字符数组可以直接赋值给字符指针,因为数组名本身就是一个指向数组首元素的指针。这样,我们就可以使用字符指针来遍历字符串,直到遇到空字符为止。

c

#include <stdio.h>int main() {// 定义一个字符数组char str[] = "hello world";// 将字符数组赋值给字符指针char *ptr = str;// 使用字符指针遍历字符串while (*ptr != '\0') {printf("%c", *ptr);ptr++;}printf("\n"); // 输出 "hello world"return 0;
}

字符串的修改

当我们需要修改字符串时,使用字符指针和字符数组会有不同的效果。对于字符数组,我们需要逐个修改数组中的元素,并确保以空字符结尾。而对于字符指针,实际上是创建了一个新的字符数组,并让指针指向这个新数组。

c

#include <stdio.h>// 修改字符数组
void modifyStringArray(char str[]) {str[0] = 'H'; // 修改第一个字符
}// 修改字符指针
void modifyStringPointer(char *str) {char newStr[] = "Hi world"; // 创建新的字符串str = newStr; // 让指针指向新的字符串
}int main() {char strArray[] = "hello world";char *strPtr = "hello world";// 修改字符数组modifyStringArray(strArray);printf("%s\n", strArray); // 输出 "Hello world"// 修改字符指针modifyStringPointer(strPtr);printf("%s\n", strPtr); // 输出 "Hi world"return 0;
}

在上面的代码中,modifyStringArray函数直接修改了传入的字符数组,而modifyStringPointer函数实际上是创建了一个新的字符串,并让指针指向这个新的字符串。

结语

通过今天的学习,我们了解了字符指针和字符串在C语言中的处理方式。希望大家能够掌握这些基本概念,并在实际编程中灵活运用。

2 指针与函数参数

嘿,同学们好!

今天我们来聊聊指针和函数参数的那些事儿。大家都知道,指针这玩意儿在C语言里头可重要了,它不仅能指向变量,还能指向数组。那你们有没有想过,指针自己能不能当数组的元素呢?当然可以啦,这就是我们说的指针数组。来,咱们先看个代码,感受一下。

c

#include <stdio.h>int main() {int var = 10;               // 一个普通的变量int arr[] = {1, 2, 3, 4};   // 一个数组int *ptrVar = &var;         // 一个指向变量的指针int (*ptrArr)[4] = &arr;    // 一个指向数组的指针,注意这里的类型printf("Value of var: %d\n", *ptrVar);printf("First element of arr: %d\n", ptrArr[0]);return 0;
}

瞧见没,ptrVar 指向了 var,而 ptrArr 指向了整个数组 arr。这就是指针的神奇之处。

指针作为函数参数

咱们再来说说函数参数。函数参数可以是任何数据类型,包括指针。当我们把指针作为参数传给函数,实际上传的是变量的地址。这有啥用呢?用处大了去了!这样,函数里对变量的修改就能影响到外面的变量。来,咱们通过代码来看看这是怎么回事。

c

#include <stdio.h>// 函数声明,参数是一个整型指针
void printValue(int *ptr);int main() {int num = 42;printValue(&num); // 传递变量的地址给函数return 0;
}// 函数定义,参数是一个整型指针
void printValue(int *ptr) {printf("The value is: %d\n", *ptr);
}

在这个例子里,printValue 函数接受一个整型指针作为参数,并打印出它指向的值。

为什么需要传递指针

有时候,我们不仅仅是想打印值,我们还想改变值。这时候,传递指针就派上用场了。为啥呢?因为只有通过指针,函数内部对变量的修改才能影响到外部变量的值。这就像是你有个朋友,你想让他帮你在一本书上做标记,你得给他书的地址,他才能找到正确的书页做标记。如果你只给他书的内容,他只能在自己的笔记本上做标记,你的书还是老样子。

c

#include <stdio.h>// 函数声明,参数是一个整型指针
void increment(int *ptr);int main() {int num = 5;printf("Before increment: %d\n", num);increment(&num); // 传递变量的地址给函数printf("After increment: %d\n", num);return 0;
}// 函数定义,参数是一个整型指针
void increment(int *ptr) {(*ptr)++; // 通过指针修改外部变量的值
}

在这个例子中,increment 函数接受一个指向 num 的指针,并将其值增加 1。由于传递的是地址,main 函数中的 num 变量的值被改变了。

练习题

来,咱们做个小练习。假设你有一个朋友,他的生日快到了,你想给他一个惊喜。你打算在你的电脑上新建一个文本文件,里面写上“生日快乐”,然后让你的程序去修改这个文件,加上他的名字。怎么实现呢?

练习题答案

这个练习题的答案可能需要用到文件操作,咱们下次课再详细讲解。不过,你可以先想想,如果你要通过程序去修改一个文件的内容,你需要做哪些步骤?

结语

好了,今天的课就到这里。希望大家能够掌握指针与函数参数的关系,以及如何通过指针来改变外部变量的值。如果有任何问题,或者想要更多的练习题,随时告诉我。下课!

3 数组名作为函数参数

好,接下来我们再给大家讲另外一个用法

咱们之前聊过指针作为函数参数,今天咱们来聊聊数组。有时候,我们需要处理的不仅仅是单个变量,还有数组。那处理数组的时候,是不是需要把数组作为参数传给函数呢?比如,我们要定义一个函数,求一个数组里面所有元素的平均数。

代码示例

c

#include <stdio.h>// 函数声明,计算数组的平均值
double getAverage(int arr[], int size);int main() {int numbers[] = {1000, 2000, 3000, 4000, 5000}; // 定义一个数组int size = sizeof(numbers) / sizeof(numbers[0]); // 计算数组的大小double avg = getAverage(numbers, size); // 计算平均值printf("The average is: %.2f\n", avg); // 打印平均值return 0;
}// 函数定义,计算数组的平均值
double getAverage(int arr[], int size) {double sum = 0; // 用于累加数组元素的和for (int i = 0; i < size; i++) {sum += arr[i]; // 累加数组元素}return sum / size; // 返回平均值
}

生活中的例子

想象一下,你有一个存钱罐,里面装了不同面额的钞票。你想知道这些钞票的平均面额,你会怎么做?你会把所有钞票加起来,然后除以钞票的数量,对吧?这个过程就和我们计算数组平均值的过程一样。

练习题

  1. 计算数组元素的总和:编写一个函数,计算一个整数数组所有元素的总和。
  2. 计算数组元素的最大值:编写一个函数,找出一个整数数组中的最大值。

练习题答案

c

#include <stdio.h>// 计算数组元素的总和
int sumArray(int arr[], int size) {int sum = 0;for (int i = 0; i < size; i++) {sum += arr[i];}return sum;
}// 找出数组中的最大值
int maxArray(int arr[], int size) {int max = arr[0];for (int i = 1; i < size; i++) {if (arr[i] > max) {max = arr[i];}}return max;
}int main() {int numbers[] = {10, 20, 30, 40, 50};int size = sizeof(numbers) / sizeof(numbers[0]);printf("Sum: %d\n", sumArray(numbers, size));printf("Max: %d\n", maxArray(numbers, size));return 0;
}

结语

好了,今天的课就到这里。希望大家能够掌握数组名作为函数参数的用法,以及如何通过函数来处理数组。如果有任何问题,或者想要更多的练习题,随时告诉我。下课!

4 返回指针的函数

引言

嘿,小伙伴们!今天我们来聊聊返回指针的函数。在C语言中,这可是个超级有用的技巧,特别是在处理字符串和数组时。就像老师在课堂上那样,咱们边讲边写代码,让你感觉就像坐在教室里一样。

为什么用char数组处理字符串

在C语言中,我们经常用char数组来处理字符串,而不是string类型。这是因为C语言是一门底层语言,它没有像C++或Java那样的string类。所以,我们得用char类型的数组来模拟字符串的行为。

示例代码

来看个例子,说明我们如何在C语言中使用char数组来处理字符串:

c

#include <stdio.h>
#include <string.h>// 函数声明,返回两个字符串中较长的那个
char* stringMax(char* str1, char* str2);int main() {char str1[] = "Hello";  // 使用char数组存储字符串char str2[] = "World!";char* longerStr = stringMax(str1, str2); // 调用函数,返回较长的字符串printf("The longer string is: %s\n", longerStr); // 打印较长的字符串return 0;
}// 函数定义,返回两个字符串中较长的那个
char* stringMax(char* str1, char* str2) {if (strlen(str1) > strlen(str2)) {return str1; // 返回较长的字符串的地址} else {return str2; // 返回较短的字符串的地址}
}

在这个例子中,str1str2都是char类型的数组,我们通过数组名来访问和操作它们,就像它们是字符串一样。

直接访问名字就是第一个字符

当我们有一个字符数组时,直接使用数组名其实就代表了数组的首地址,也就是第一个字符的地址。这意味着,当你传递一个字符数组给函数时,你实际上是在传递一个指向数组第一个元素的指针。

示例代码

c

#include <stdio.h>void printFirstChar(char str[]) {printf("The first character is: %c\n", str[0]); // 直接访问数组名,取得第一个字符
}int main() {char str[] = "Hello, World!";printFirstChar(str); // 传递数组名,相当于传递第一个字符的地址return 0;
}

在这个例子中,printFirstChar函数接收一个char类型的数组str。当我们调用printFirstChar(str)时,我们实际上是传递了str数组的首地址,也就是str[0]的地址。在函数内部,str就代表了这个地址,所以str[0]就是数组的第一个元素。

练习题

  1. 找出数组中的最大值:编写一个函数,找出一个整数数组中的最大值,并返回其地址。
  2. 字符串复制:编写一个函数,实现字符串的复制功能,并返回复制后的字符串地址。

练习题答案

c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>// 找出数组中的最大值
int* findMax(int arr[], int size) {int* maxPtr = arr; // 假设第一个元素是最大的for (int i = 1; i < size; i++) {if (arr[i] > *maxPtr) {maxPtr = &arr[i]; // 更新最大值的地址}}return maxPtr; // 返回最大值的地址
}// 字符串复制
char* stringCopy(char* src) {char* dest = malloc(strlen(src) + 1); // 分配内存strcpy(dest, src); // 复制字符串return dest; // 返回复制后的字符串地址
}int main() {int arr[] = {10, 20, 30, 40, 50};int size = sizeof(arr) / sizeof(arr[0]);int* maxVal = findMax(arr, size);printf("The max value is: %d\n", *maxVal); // 打印最大值char str[] = "Hello, World!";char* copiedStr = stringCopy(str);printf("Copied string: %s\n", copiedStr); // 打印复制后的字符串free(copiedStr); // 释放内存return 0;
}

结语

好了,今天的课就到这里。希望大家能够掌握返回指针的函数的用法,以及如何通过函数来处理字符串和数组。如果有任何问题,或者想要更多的练习题,随时告诉我。下课!

5 返回指针的函数之生成随机数组

引言

大家好!今天我们来探讨一个非常实用的C语言技巧——返回指针的函数,特别是如何用它来生成一个随机数组。这不仅是一个技术点,也是一个实际编程中经常用到的技巧。

返回指针的函数和随机数组

代码示例

下面是一个生成并返回包含10个随机整数数组的函数示例:

c

#include <stdio.h>
#include <stdlib.h>
#include <time.h>// 函数声明,生成并返回一个包含10个随机整数的数组
int* generateRandomArray();int main() {int* randomArray = generateRandomArray(); // 调用函数,返回随机数组的指针for (int i = 0; i < 10; i++) {printf("randomArray[%d] = %d\n", i, randomArray[i]); // 打印数组元素}return 0;
}// 函数定义,生成并返回一个包含10个随机整数的数组
int* generateRandomArray() {static int a[10]; // 使用static数组,使其在函数调用后仍然存在srand(time(NULL)); // 使用当前时间作为种子初始化随机数生成器for (int i = 0; i < 10; i++) {a[i] = rand() % 100; // 生成0到99之间的随机数}return a; // 返回数组的首地址
}

解释 srand(time(NULL));

  • srand 是用来设置随机数生成器种子的函数。
  • time 函数返回当前时间的秒数,用作随机数生成器的种子,以确保每次程序运行时生成的随机数序列都不同。
  • time(NULL) 表示不需要指定时间,NULL 是一个空指针,表示不传递任何参数给 time 函数。

解释 a[i] = rand() % 100;

  • rand() 函数生成一个随机整数。
  • % 是取模运算符,用于计算两个数相除后的余数。
  • rand() % 100 确保生成的随机数在0到99之间。

生活中的例子

想象一下,你有一个装满100个不同颜色球的袋子,每次随机抽取一个球。rand() 函数就像从袋子里随机抽取一个球,而 % 100 确保你总是从1到100号球中抽取。

练习题

  1. 生成斐波那契数列:编写一个函数,生成一个包含10个斐波那契数的数组,并返回这个数组的指针。
  2. 查找数组中的最大值:编写一个函数,接受一个整数数组的指针和数组的大小,返回数组中最大值的指针。

练习题答案

c

#include <stdio.h>// 生成斐波那契数列
int* fibonacciSequence() {static int fib[10];fib[0] = 0;fib[1] = 1;for (int i = 2; i < 10; i++) {fib[i] = fib[i - 1] + fib[i - 2];}return fib;
}// 查找数组中的最大值
int* findMaxValue(int arr[], int size) {static int max;max = arr[0];for (int i = 1; i < size; i++) {if (arr[i] > max) {max = arr[i];}}return &max;
}int main() {int* fibSeq = fibonacciSequence();for (int i = 0; i < 10; i++) {printf("Fibonacci[%d] = %d\n", i, fibSeq[i]);}int arr[] = {3, 5, 7, 2, 8, 10, 6};int* maxVal = findMaxValue(arr, sizeof(arr) / sizeof(arr[0]));printf("Max value in array is: %d\n", *maxVal);return 0;
}

结语

希望今天的课程能帮助你理解返回指针的函数,特别是如何用它来生成随机数组。如果你有任何问题,或者想要更多的练习题,随时告诉我。下课!

6 函数指针

引言

函数指针,听起来是不是有点高大上?但其实它的概念并不复杂。在我们之前的课程中,我们聊过指针数组和数组指针,现在我们来聊聊函数指针。函数指针的本质是一个指针,它指向的是一个函数,而不是一个普通的数据。

函数指针的概念

函数指针和数组指针很像,数组指针指向的是数组的首元素,而函数指针指向的是函数的代码区域的起始地址。

代码示例

下面是一个使用函数指针的示例:

c

#include <stdio.h>// 定义一个返回两个整数中较大值的函数
int max(int a, int b) {return a > b ? a : b;
}int main() {// 定义一个函数指针int (*fp)(int, int) = max; // 将函数指针指向max函数int result = fp(10, 20); // 使用函数指针调用函数printf("The larger number is: %d\n", result);return 0;
}

在这个例子中,fp 是一个函数指针,它指向了 max 函数。我们可以通过 fp 来调用 max 函数,就像直接调用 max 一样。

解释 srand(time(NULL));

  • srand 是用来设置随机数生成器种子的函数。
  • time 函数返回当前时间的秒数,用作随机数生成器的种子,以确保每次程序运行时生成的随机数序列都不同。
  • time(NULL) 表示不需要指定时间,NULL 是一个空指针,表示不传递任何参数给 time 函数。

生活中的例子

想象一下,你有一本菜谱,每道菜都有一个对应的页码。当你想吃某道菜时,你可以通过页码直接翻到那一页。函数指针就像是菜谱中的页码,它指向了特定的函数(菜谱中的特定页面),你可以通过函数指针直接“翻到”并执行那个函数。

练习题

  1. 计算两个数的和:编写一个函数,返回两个整数的和,并使用函数指针调用这个函数。
  2. 字符串比较:编写一个函数,比较两个字符串是否相等,并使用函数指针调用这个函数。

练习题答案

c

#include <stdio.h>
#include <string.h>// 计算两个数的和
int add(int a, int b) {return a + b;
}// 字符串比较
int strcmpEqual(char* str1, char* str2) {return strcmp(str1, str2) == 0;
}int main() {// 定义函数指针int (*fpAdd)(int, int) = add;int resultAdd = fpAdd(5, 7); // 使用函数指针调用add函数printf("The sum is: %d\n", resultAdd);// 定义函数指针int (*fpStrCmp)(char*, char*) = strcmpEqual;char str1[] = "Hello";char str2[] = "Hello";int resultStrCmp = fpStrCmp(str1, str2); // 使用函数指针调用strcmpEqual函数printf("Strings are equal: %s\n", resultStrCmp ? "Yes" : "No");return 0;
}

结语

希望今天的课程能帮助你理解函数指针的概念和用法。如果你有任何问题,或者想要更多的练习题,随时告诉我。下课!

7 回调函数

引言

上节课我们讲了函数指针的基本概念,但你可能会觉得这玩意儿到底有啥用。别急,今天我们就来聊聊回调函数,这是函数指针的一个实际应用,非常强大。

回调函数的概念

回调函数其实就是函数指针的应用。你可以把一个函数的入口地址当成参数传给另一个函数,这样你就可以在那个函数内部调用你传入的函数了。

代码示例

下面是一个使用回调函数的示例:

c

#include <stdio.h>
#include <stdlib.h>// 定义一个回调函数类型
typedef int (*CallbackFunc)(int);// 一个简单的回调函数,返回传入值的两倍
int doubleValue(int value) {return value * 2;
}// 使用回调函数初始化数组
void initializeArray(int arr[], int size, CallbackFunc func) {for (int i = 0; i < size; i++) {arr[i] = func(i); // 使用回调函数初始化数组元素}
}int main() {int arr[10];initializeArray(arr, 10, doubleValue); // 使用回调函数初始化数组for (int i = 0; i < 10; i++) {printf("arr[%d] = %d\n", i, arr[i]);}return 0;
}

在这个例子中,initializeArray 函数接受一个数组、数组的大小和一个回调函数作为参数。它使用回调函数来初始化数组的每个元素。

生活中的例子

想象一下,你有一个遥控器,每个按钮都对应不同的功能。当你按下一个按钮时,就会触发相应的功能。这就像回调函数一样,你传入的函数(功能)在另一个函数(遥控器)中被调用。

练习题

  1. 计算数组元素的和:编写一个函数,接受一个整数数组和它的大小,使用回调函数计算数组元素的和。
  2. 查找数组中的最大值:编写一个函数,接受一个整数数组和它的大小,使用回调函数查找数组中的最大值。

练习题答案

c

#include <stdio.h>// 计算数组元素的和
int sumArray(int arr[], int size, int (*callback)(int)) {int sum = 0;for (int i = 0; i < size; i++) {sum += callback(arr[i]); // 使用回调函数处理每个元素}return sum;
}// 查找数组中的最大值
int findMax(int arr[], int size, int (*callback)(int)) {int max = callback(arr[0]); // 假设第一个元素是最大的for (int i = 1; i < size; i++) {if (callback(arr[i]) > max) {max = callback(arr[i]);}}return max;
}int main() {int arr[] = {3, 5, 7, 2, 8, 10, 6};int size = sizeof(arr) / sizeof(arr[0]);int sum = sumArray(arr, size, abs); // 使用绝对值作为回调函数计算和printf("Sum of array elements: %d\n", sum);int max = findMax(arr, size, abs); // 使用绝对值作为回调函数查找最大值printf("Max value in array: %d\n", max);return 0;
}

结语

希望今天的课程能帮助你理解回调函数的概念和用法。如果你有任何问题,或者想要更多的练习题,随时告诉我。下课!

8 多级指针

引言

讲完了指针和函数的基本对应关系,我们来聊聊指向指针的指针,也就是多级指针。这玩意儿听起来可能有点绕,但其实理解了基本概念后,你会发现它还是挺直观的。

多级指针的概念

多级指针,顾名思义,就是指针的指针。你可以想象成套娃,一层套一层。在C语言中,我们可以有一级指针、二级指针,甚至更多级。

代码示例

下面是一个多级指针的示例:

c

#include <stdio.h>int main() {int a = 100;                 // 原始变量int *p1 = &a;                // 一级指针,指向变量aint **p2 = &p1;              // 二级指针,指向一级指针p1int ***p3 = &p2;             // 三级指针,指向二级指针p2printf("Value of a: %d\n", a);      // 直接打印变量a的值printf("Value of a through p1: %d\n", *p1); // 通过一级指针打印变量a的值printf("Value of a through p2: %d\n", **p2); // 通过二级指针打印变量a的值printf("Value of a through p3: %d\n", ***p3); // 通过三级指针打印变量a的值return 0;
}

在这个例子中,我们定义了一个变量a,然后定义了三个指针p1p2p3,分别指向ap1p2。通过逐级解引用,我们最终都能获取到变量a的值。

生活中的例子

想象一下,你有一个装满盒子的抽屉。你想要最里面盒子里的东西,但你得先打开抽屉(一级指针),然后打开盒子(二级指针),最后才能拿到你想要的东西(三级指针)。每打开一层,你就更接近目标一步。

练习题

  1. 打印多级指针指向的值:编写一个程序,定义一个变量和它的多级指针,打印出通过多级指针指向的变量值。
  2. 多级指针赋值:编写一个程序,通过多级指针给变量赋值,并打印出赋值后的结果。

练习题答案

c

#include <stdio.h>int main() {int a = 10;                 // 原始变量int *p1 = &a;               // 一级指针,指向变量aint **p2 = &p1;             // 二级指针,指向一级指针p1int ***p3 = &p2;            // 三级指针,指向二级指针p2// 打印多级指针指向的值printf("Direct value of a: %d\n", a);printf("Value of a through p1: %d\n", *p1);printf("Value of a through p2: %d\n", **p2);printf("Value of a through p3: %d\n", ***p3);// 多级指针赋值***p3 = 20;                 // 通过三级指针给变量a赋值printf("New value of a: %d\n", a);return 0;
}

结语

希望今天的课程能帮助你理解多级指针的概念和用法。如果你有任何问题,或者想要更多的练习题,随时告诉我。下课!

9 空指针和野指针

引言

大家好,今天我们来聊聊C语言中的空指针和野指针。空指针是C语言中一个特殊的指针值,用NULL表示。而野指针,顾名思义,就是“野路子”的指针,指向不确定的地方。

空指针

空指针是C语言中用来表示“没有指向任何东西”的指针。它实际上是一个宏定义,值为0

代码示例

c

#include <stdio.h>
#include <stdlib.h>int main() {int *p = NULL; // 定义一个空指针if (p == NULL) {printf("Pointer p is empty.\n");} else {printf("Pointer p is not empty.\n");}return 0;
}

在这个例子中,我们定义了一个空指针p,并检查它是否为空。这是一个好习惯,可以避免很多潜在的错误。

野指针

野指针是指那些没有被初始化或者指向了不应该指向的地方的指针。

代码示例

c

#include <stdio.h>int main() {int *p; // 未初始化的指针,野指针// 以下操作是不安全的,因为p可能指向一个随机的内存地址*p = 100; // 尝试修改p指向的值return 0;
}

在这个例子中,我们定义了一个未初始化的指针p,然后尝试修改它指向的值。这是非常危险的,因为你不知道p实际上指向了哪里。

生活中的例子

想象一下,你手上有一把钥匙,但你不知道这把钥匙能开哪扇门。如果你随便试,可能会打开别人的家门,这就是“野指针”。而空指针就像是一把你知道不能开任何门的钥匙,至少它不会给你带来麻烦。

练习题

  1. 检查空指针:编写一个函数,检查传入的指针是否为空,并返回相应的提示。
  2. 安全地使用指针:编写一个函数,安全地给一个指针赋值,确保它不会成为野指针。

练习题答案

c

#include <stdio.h>
#include <stdlib.h>// 检查空指针
void checkNullPointer(int *p) {if (p == NULL) {printf("Pointer is empty.\n");} else {printf("Pointer is not empty.\n");}
}// 安全地给指针赋值
void safeAssign(int **p) {if (*p == NULL) {*p = (int *)malloc(sizeof(int)); // 分配内存if (*p != NULL) {**p = 100; // 赋值}}
}int main() {int *ptr = NULL;checkNullPointer(ptr); // 检查空指针safeAssign(&ptr); // 安全地给指针赋值checkNullPointer(ptr); // 再次检查空指针free(ptr); // 释放内存return 0;
}

结语

希望今天的课程能帮助你理解空指针和野指针的概念和用法。如果你有任何问题,或者想要更多的练习题,随时告诉我。下课!

10 指针用法总览

引言

大家好,今天我们把所有关于指针的内容来个大总结。指针这东西,一开始学可能觉得有点绕,但其实它们的概念和用法都是有规律的。

指针的定义和用法

基本变量

c

int a; // 定义一个整型变量

这是最基本的,定义了一个整型变量a

指针

c

int *p; // 定义一个指向整型变量的指针

这里定义了一个指针p,它指向一个整型变量。

数组

c

int arr[5]; // 定义一个长度为5的整型数组

arr是一个数组,包含5个整型元素。

指针数组

c

int *arr_p[5]; // 定义一个包含5个指向整型变量的指针的数组

arr_p是一个指针数组,包含5个指向整型变量的指针。

数组指针

c

int (**arr_pp)[4]; // 定义一个指向包含4个整型元素的数组的指针

arr_pp是一个数组指针,指向一个包含4个整型元素的数组。

函数

c

int f(int x); // 定义一个接受整型参数并返回整型值的函数

f是一个函数,接受一个整型参数x,并返回一个整型值。

指针函数

c

int *f_p(); // 定义一个返回指向整型变量的指针的函数

f_p是一个函数,返回一个指向整型变量的指针。

函数指针

c

int (*fp)(); // 定义一个指向返回整型值且无参数的函数的指针

fp是一个函数指针,指向一个返回整型值且无参数的函数。

多级指针

c

int **p_pp; // 定义一个指向指针的指针

p_pp是一个二级指针,指向一个指针。

生活中的例子

想象一下,你有一堆书(数组),每本书都有一个书名(指针),你可以快速找到任何一本书。如果你有一个书单(指针数组),每个书单上都列着一些书名,那你就可以管理多个书堆。如果你有一个指向书单的指针(数组指针),那你就可以改变书单的内容,比如添加或删除书单。这就是指针在内存中的管理方式。

练习题

  1. 定义和初始化指针数组:编写一个程序,定义一个指针数组,并初始化为指向几个不同整数的指针。
  2. 使用函数指针调用函数:编写一个程序,定义一个函数和一个函数指针,通过函数指针调用该函数。

练习题答案

c

#include <stdio.h>// 函数定义
int add(int a, int b) {return a + b;
}int main() {// 定义和初始化指针数组int num1 = 10, num2 = 20, num3 = 30;int *arr_p[] = {&num1, &num2, &num3};for (int i = 0; i < 3; i++) {printf("Value: %d\n", *arr_p[i]);}// 使用函数指针调用函数int (*fp)(int, int) = add; // 定义函数指针并指向add函数printf("Sum: %d\n", fp(10, 20)); // 通过函数指针调用函数return 0;
}

结语

希望今天的总结能帮助你更好地理解指针的概念和用法。如果你有任何问题,或者想要更多的练习题,随时告诉我。下课!

11 预处理指令基本概念和用法

引言

大家好,今天我们来聊聊预处理器。之前我们说过,C语言处理程序分为好几个阶段,第一步就是预处理,之后才是编译、汇编、链接,最后生成可执行文件执行。所以,预处理器就是对我们代码进行预处理的各种操作。

预处理指令的基本概念

预处理指令主要就包括宏替换、文件包含和条件编译这三种。这些指令都是以井号(#)开头的,这是预处理指令的基本格式。

代码示例

c

// 宏替换示例
#include <stdio.h>#define PI 3.14159int main() {printf("The value of PI is: %f\n", PI);return 0;
}

在这个例子中,我们定义了一个宏PI,然后在main函数中使用它。

c

// 文件包含示例
#include <stdio.h>// 引入头文件
#include "my_header.h"int main() {// 使用在my_header.h中定义的函数myFunction();return 0;
}

在这个例子中,我们使用#include指令来包含一个头文件my_header.h

c

// 条件编译示例
#include <stdio.h>#define DEBUGint main() {#ifdef DEBUGprintf("This is a debug message.\n");#endifreturn 0;
}

在这个例子中,我们使用#ifdef#endif来条件编译调试信息。

生活中的例子

想象一下,你在做蛋糕。预处理指令就像是食谱中的步骤说明。比如,“将2杯面粉和1杯糖混合”(宏替换),“加入1个鸡蛋”(文件包含),“如果喜欢甜,可以加更多糖”(条件编译)。这些步骤指导你完成整个蛋糕的制作。

练习题

  1. 定义一个宏并使用它:编写一个程序,定义一个宏MAX,用于比较两个数的大小,并返回较大的那个数。
  2. 使用文件包含:编写一个程序,创建两个文件header.hmain.c,在header.h中定义一个函数add,在main.c中包含header.h并调用add函数。

练习题答案

c

// max.c
#include <stdio.h>// 定义一个宏比较两个数的大小
#define MAX(a, b) ((a) > (b) ? (a) : (b))int main() {int x = 10;int y = 20;printf("The maximum is: %d\n", MAX(x, y));return 0;
}

c

// header.h
#ifndef HEADER_H
#define HEADER_Hint add(int a, int b);#endif

c

// add.c
#include "header.h"int add(int a, int b) {return a + b;
}

c

// main.c
#include <stdio.h>
#include "header.h"int main() {printf("The sum is: %d\n", add(5, 7));return 0;
}

结语

希望今天的课程能帮助你理解预处理指令的概念和用法。如果你有任何问题,或者想要更多的练习题,随时告诉我。下课!

12 宏定义基本用法

引言

大家好,今天我们来聊聊宏定义。宏定义其实就是用一个标志符来表示一个全局替换的文本。这个标志符,我们通常称之为宏名,用来替换代码中的一段文本。

宏定义的基本概念

宏定义可以用#define指令来实现,它通常用于定义全局常量或者新的数据类型。

代码示例

c

#include <stdio.h>// 定义一个宏PI,用于表示圆周率
#define PI 3.14159int main() {double radius = 5.0;double area = PI * radius * radius; // 使用宏PI计算圆的面积printf("The area of the circle is: %f\n", area);return 0;
}

在这个例子中,我们定义了一个宏PI,并用它来计算圆的面积。

定义新的数据类型

c

#include <stdio.h>// 定义布尔类型
#define BOOL int
#define TRUE 1
#define FALSE 0int main() {BOOL is_valid = TRUE;if (is_valid) {printf("This is valid.\n");}return 0;
}

在这个例子中,我们用宏定义来模拟布尔类型。

宏定义的嵌套和取消定义

c

#include <stdio.h>#define PI 3.14159
#define AREA(r) (PI * r * r) // 计算圆的面积int main() {double radius = 2.0;double area = AREA(radius); // 使用宏AREA计算圆的面积printf("The area of the circle is: %f\n", area);return 0;
}

在这个例子中,我们定义了一个宏AREA来计算圆的面积,并使用了宏PI

生活中的例子

想象一下,你在做蛋糕。宏定义就像是食谱中的“糖=2杯”或者“面粉=3杯”。当你看到“糖”或者“面粉”时,你就知道需要加多少。这就是宏定义的作用,它用一个简单的名称来替换一个复杂的值或者操作。

练习题

  1. 定义一个宏并使用它:编写一个程序,定义一个宏MAX,用于比较两个数的大小,并返回较大的那个数。
  2. 使用宏定义计算面积:编写一个程序,定义一个宏CIRCLE_AREA,用于计算圆的面积。

练习题答案

c

// 练习题1
#include <stdio.h>#define MAX(a, b) ((a) > (b) ? (a) : (b))int main() {int x = 10;int y = 20;printf("The maximum is: %d\n", MAX(x, y));return 0;
}

c

// 练习题2
#include <stdio.h>#define PI 3.14159
#define CIRCLE_AREA(r) (PI * (r) * (r))int main() {double radius = 5.0;double area = CIRCLE_AREA(radius);printf("The area of the circle is: %f\n", area);return 0;
}

结语

希望今天的课程能帮助你理解宏定义的概念和用法。如果你有任何问题,或者想要更多的练习题,随时告诉我。下课!

13 带参数的宏定义

引言

大家好,今天我们来聊聊带参数的宏定义。宏定义在C语言中可是个强大的工具,尤其是当你需要定义一些可以带参数的宏时,它就显得更加灵活了。

带参数宏定义的基本概念

带参数的宏定义允许你定义一个宏,它可以接受参数,就像函数一样。这些参数在宏展开时会被替换成具体的值。

代码示例

c

#include <stdio.h>// 定义一个带参数的宏,计算两个数的最大值
#define MAX(x, y) ((x) > (y) ? (x) : (y))int main() {int a = 10;int b = 20;printf("The maximum of %d and %d is: %d\n", a, b, MAX(a, b));return 0;
}

在这个例子中,我们定义了一个宏MAX,它接受两个参数xy,并返回它们中的最大值。

生活中的例子

想象一下,你在厨房里做饭,需要根据菜谱上的配方(宏定义)来添加调料。这个配方可能会告诉你,如果你有更多糖,就多加点(带参数的宏),否则就少加点。这就是带参数宏在实际生活中的应用。

注意事项

  1. 参数之间的空格:在宏定义中,参数之间可以有空格,但在宏名和参数列表之间不能有空格。
  2. 参数替换:在宏展开时,参数会被替换成具体的值,所以需要确保参数在宏体中被正确使用。
  3. 避免副作用:如果宏参数涉及到递增或递减操作,可能会导致意想不到的结果,因为宏会将表达式展开多次。

代码示例 - 避免副作用

c

#include <stdio.h>// 定义一个带参数的宏,计算平方
#define SQUARE(x) ((x) * (x))int main() {int i = 1;printf("The square of %d is: %d\n", i, SQUARE(i++));printf("The value of i after increment is: %d\n", i);return 0;
}

在这个例子中,我们定义了一个宏SQUARE,它接受一个参数x,并返回它的平方。注意,我们使用了i++,这意味着i的值在宏展开后会增加。

练习题

  1. 定义一个带参数的宏:编写一个程序,定义一个宏MIN,用于比较两个数的大小,并返回较小的那个数。
  2. 使用带参数的宏:编写一个程序,使用宏SQUARE计算一个数的平方,并打印结果。

练习题答案

c

// 练习题1
#include <stdio.h>#define MIN(x, y) ((x) < (y) ? (x) : (y))int main() {int a = 5;int b = 7;printf("The minimum of %d and %d is: %d\n", a, b, MIN(a, b));return 0;
}

c

// 练习题2
#include <stdio.h>#define SQUARE(x) ((x) * (x))int main() {int num = 4;printf("The square of %d is: %d\n", num, SQUARE(num));return 0;
}

结语

希望今天的课程能帮助你理解带参数的宏定义的概念和用法。如果你有任何问题,或者想要更多的练习题,随时告诉我。下课!

14 文件包含

引言

大家好,今天我们来聊聊文件包含。这个指令非常简单,就是include,我们用它来引入标准库中的头文件。头文件就是那些以点.h结尾的文件,它们包含了函数的声明和宏定义等。

文件包含的基本概念

代码示例

c

#include <stdio.h>// 自定义头文件
#include "myheader.h"int main() {printf("My header file is included.\n");return 0;
}

在这个例子中,我们包含了标准库的头文件stdio.h和自定义的头文件myheader.h

生活中的例子

想象一下,你在做一个水果沙拉。你需要各种水果,比如苹果、香蕉和橘子。这些水果就像库文件,而你的沙拉碗就像你的程序。你需要把各种水果(库文件)加到碗里(程序)才能完成你的沙拉(程序)。

自定义头文件

我们可以创建自己的头文件,将常用的函数声明、宏定义等放在里面,然后在需要的地方包含它们。

代码示例

c

// myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H#define PI 3.14159
void printHello();#endif

c

// printHello.c
#include "myheader.h"void printHello() {printf("Hello, world!\n");
}

c

// main.c
#include "myheader.h"int main() {printHello();printf("PI is: %f\n", PI);return 0;
}

在这个例子中,我们创建了一个头文件myheader.h,它包含了一个宏定义和一个函数声明。然后在main.c中包含了这个头文件,并使用了里面的宏和函数。

练习题

  1. 创建一个头文件:编写一个头文件,定义一个宏MAX和一个函数compare,用于比较两个整数的大小。
  2. 使用头文件:在主函数中包含这个头文件,并使用MAX宏和compare函数。

练习题答案

c

// max_compare.h
#ifndef MAX_COMPARE_H
#define MAX_COMPARE_H#define MAX(a, b) ((a) > (b) ? (a) : (b))
int compare(int x, int y);#endif

c

// compare.c
#include "max_compare.h"int compare(int x, int y) {if (x > y) {return 1;} else if (x < y) {return -1;} else {return 0;}
}

c

// main.c
#include "max_compare.h"int main() {int a = 10;int b = 20;printf("The maximum is: %d\n", MAX(a, b));printf("Comparison result: %d\n", compare(a, b));return 0;
}

结语

希望今天的课程能帮助你理解文件包含的概念和用法。如果你有任何问题,或者想要更多的练习题,随时告诉我。下课!

15 条件编译

引言

大家好,今天我们来聊聊条件编译。条件编译就是根据一定的条件,决定代码的某一部分是否需要编译。这有点像我们生活中的开关,根据需要打开或关闭某些设备。

条件编译的基本概念

条件编译使用预处理指令实现,主要的指令有#ifdef#ifndef#if#else#endif

代码示例

c

#include <stdio.h>// 定义一个宏DEBUG
#define DEBUG 1int main() {
#ifdef DEBUGprintf("This is a debug message.\n");
#elseprintf("Debug is not enabled.\n");
#endifreturn 0;
}

在这个例子中,我们定义了一个宏DEBUG,然后在#ifdef DEBUG#else之间编写了不同的代码。根据DEBUG宏是否定义,编译器会选择编译哪一部分代码。

生活中的例子

想象一下,你在家里看电视。电视遥控器上有“静音”和“非静音”两个按钮,这两个按钮就像是条件编译中的#ifdef#else。当你按下“静音”按钮时,电视就不会发出声音(相当于代码被“编译”但不执行);当你按下“非静音”按钮时,电视就会发出声音(相当于代码被“编译”并执行)。

练习题

  1. 使用条件编译输出不同的消息:编写一个程序,根据宏RELEASE的值输出不同的消息。
  2. 条件编译中的多分支选择:编写一个程序,使用#if#elif#else实现多分支选择。

练习题答案

c

// 练习题1
#include <stdio.h>// 定义一个宏RELEASE
#define RELEASE 1int main() {
#if RELEASEprintf("This is a release version.\n");
#elseprintf("This is a development version.\n");
#endifreturn 0;
}

c

// 练习题2
#include <stdio.h>int main() {int condition = 2;#if condition == 1printf("Condition is 1.\n");
#elif condition == 2printf("Condition is 2.\n");
#elseprintf("Condition is neither 1 nor 2.\n");
#endifreturn 0;
}

结语

希望今天的课程能帮助你理解条件编译的概念和用法。如果你有任何问题,或者想要更多的练习题,随时告诉我。下课!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/58706.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++初阶——类与对象(上篇)

一、写在前面 类与对象是C不同于C语言的一个板块&#xff0c;内容很多&#xff0c;笔者把这部分分为三篇博客来讲解&#xff0c;希望能够帮助各位读者更容易地理解这些知识点。弄清楚这一部分之后&#xff0c;C就算是成功入门了。 二、面向过程和面向对象 C语言就是典型的面向…

Java如何实现PDF转高质量图片

大家好&#xff0c;我是 V 哥。在Java中&#xff0c;将PDF文件转换为高质量的图片可以使用不同的库&#xff0c;其中最常用的库之一是 Apache PDFBox。通过该库&#xff0c;你可以读取PDF文件&#xff0c;并将每一页转换为图像文件。为了提高图像的质量&#xff0c;你可以指定分…

论文略读:OneChart: Purify the Chart Structural Extraction via One Auxiliary Token

2024 旷视的work 图表解析模型 1 背景 对于之前的视觉语言模型&#xff0c;论文认为其有两点不足需要改进&#xff1a; 需要充分训练一个真正会看 chart 的 vision encoder单纯对文本输出算交叉熵损失&#xff0c;并不是最优的&#xff08;如上图所示&#xff0c;当ground-tr…

STM32CubeMX学习(三) SPI+DMA通信

STM32CubeMX学习&#xff08;三&#xff09; SPIDMA通信 一、简介二、新建STM32CubeMX项目并使用外部时钟三、SPI3配置四、相关代码五、测试 一、简介 本文将基于STM32F103RCT芯片介绍如何在STM32CubeMXKEIL5开发环境下进行SPIDMA通信。 操作系统&#xff1a;WIN10 x64硬件电…

iOS静态库(.a)及资源文件的生成与使用详解(OC版本)

引言 iOS静态库&#xff08;.a&#xff09;及资源文件的生成与使用详解&#xff08;Swift版本&#xff09;_xcode 合并 .a文件-CSDN博客 在前面的博客中我们已经介绍了关于iOS静态库的生成步骤以及关于资源文件的处理&#xff0c;在本篇博客中我们将会以Objective-C为基础语言…

Python爬虫:在1688上“拍立淘”——按图索骥的奇妙之旅

想象一下&#xff0c;你是一名古代的侦探&#xff0c;手中握着一张神秘的藏宝图&#xff0c;在1688的茫茫商品海洋中寻找与之匹配的宝藏。今天&#xff0c;我们将一起化身为代码界的“拍立淘”专家&#xff0c;使用Python爬虫技术&#xff0c;通过API接口按图搜索商品。准备好你…

如何在小红书发布笔记时显示外地IP地址

小红书平台在发布笔记时显示IP地址可能是由于网络爬虫或者某些技术手段抓取数据时所导致的。为了保护用户隐私和安全&#xff0c;显示外地IP地址&#xff0c;可以尝试以下几种方法&#xff1a; 1.检查发布环境&#xff1a; 确保你是在一个安全、可信的网络环境下发布笔记&…

Linux中查询Redis中的key和value(没有可视化工具)

1.进入redis安装目录 进入redis安装目录,找到redis-cli(redis的客户端) 2.登录redis客户端 登录redis的客户端,格式:redis-cli -h [host] -p [port] -a [password],懂的都懂!!! ./redis-cli -h 192.168.8.101 -p 6380 -a xxxx登录成功后就这样子 3.查看redis中所有的key和…

Unity Editor 快速移动资源

Editor 快速移动资源 &#x1f354;使用场景&#x1f32d;功能 &#x1f354;使用场景 一般想要移动一个资源到另一个目录的办法是选中资源拖拽过去&#xff0c; 但在一个比较大的项目中你得一直拖啊拖直到找到那个目录 &#x1f92f;。 使用本插件就可以省去拖拽的步骤&#…

特斯联巨亏数十亿:毛利率剧烈波动下滑,高管动荡引发关注

《港湾商业观察》施子夫 近期&#xff0c;重庆特斯联智慧科技股份有限公司&#xff08;以下简称&#xff0c;特斯联&#xff09;递表港交所&#xff0c;联席保荐机构中信证券和海通国际。 此番闯关港交所&#xff0c;特斯联三年半巨亏超70亿元&#xff0c;公司何时能扭亏为盈…

javaweb----VS code

前端开发神器&#xff1a;VS Code → 速度快、体积小、插件多 VS Code 安装官网&#xff1a;https://code.visualstudio.com/download VS Code一些必备的插件安装&#xff1a; 1、Chinese (Simplified) 简体中文 2、Code Spell Checker 检查拼写 3、HTML CSS Support 4…

使用 Kafka 和 MinIO 实现人工智能数据工作流

MinIO Enterprise Object Store 是用于创建和执行复杂数据工作流的基础组件。此事件驱动功能的核心是使用 Kafka 的 MinIO 存储桶通知。MinIO Enterprise Object Store 为所有 HTTP 请求&#xff08;如 PUT、POST、COPY、DELETE、GET、HEAD 和 CompleteMultipartUpload&#xf…

Linux: network: hw csum failure

文章目录 简介openvswitchifb: fix packets checksummellanoxiommu=ptmellanox 2mellanox 3建议简介 这里总结一下几个checksum的问题,仅供参考。需要看所使用的系统是否已经有了相应的fix。也可能是一个新问题,如果是新问题,恭喜发现了新的宝藏。 openvswitch Fix setti…

【Python】数据容器详解:列表、元组、字典与集合的推导式与公共方法

目录 &#x1f354; 列表集合字典的推导式 1.1 什么是推导式 1.2 为什么需要推导式 1.3 列表推导式 1.4 列表推导式 if条件判断 1.5 for循环嵌套列表推导式 1.6 字典推导式 1.7 集合推导式 &#x1f354; 数据序列中的公共方法 2.1 什么是公共方法 2.2 常见公共方法…

【PythonWeb开发】Flask-RESTful视图类基础知识

flask_restful 是一个扩展库&#xff0c;它为 Flask 提供了快速构建 RESTful API 的功能。使用 flask_restful 可以简化 RESTful API 的开发过程&#xff0c;减少样板代码&#xff0c;并且提供了一些高级特性&#xff0c;如 HTTP 方法的映射、资源路由的定义等。 在flask_restf…

1 C++ 编译属性 __attribute__((X))

__attribute__是GNU对标准C的扩展&#xff0c;可以用来设置函数属性&#xff08;Function Attribute&#xff09;、变量属性&#xff08;Variable Attribute&#xff09;和类型属性&#xff08;Type Attribute&#xff09;。 __attribute__使用 1 __attribute__((used))函数…

uniapp 使用uni.getRecorderManager录音,wav格式采样率低于44100,音频播放不了问题解决

如题&#xff1a;uniapp开发app端&#xff0c;使用uni.getRecorderManager录wav格式音频&#xff0c;采样率8000/16000都无法播放&#xff0c;44100可以播放。但由于项目需求需要录制采样率为8000的音频&#xff0c;于是引用了如下插件 插件地址(具体可以参考该插件的使用说明…

笔记:mysql升级 5.6至5.7

说明 一台已有数据的机器&#xff0c;停机升级&#xff0c;从MySQL Server5.6.48&#xff0c;升级到 5.7.38。 环境介绍 10.24.10.247&#xff0c;Mysql 5.6.48 CentOS Linux release 7.9.2009 (Core) 32G内存、500G数据盘/home&#xff1b; 实际数据量约120M&#xff0c;2个…

基于Unet卷积神经网络的脑肿瘤MRI分割

项目源码获取方式见文章末尾&#xff01; 回复暗号&#xff1a;13&#xff0c;免费获取600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【YOLO模型实现农作物病虫害虫识别带GUI界面】 2.【卫星图像道路检测DeepLabV3P…

uni-app在H5页面唤起小程序登录 然后再回到当前页面

在H5页面触发小程序方法跳转到登录页面 wx.miniProgram.navigateTo({ url: /pages/login/index?webviewRedirect encodeURIComponent(redirectUrl) }); 携带对应的返回地址 在等于成功之后触发webViewPage 携带token if (this.webviewRedirect) { const tempUrl decodeU…