当前位置: 首页 > news >正文

C语言基础—(函数,指针与形参实参,字符串与指针,结构体)

目录

一:函数基础

1. 函数定义

2. 函数的作用

3. 函数格式

 4. 函数形式与使用

5. 函数定义的注意事项 

二:形参实参

1. 实参实参

2.  值传递

1. 普通变量值传递

1. 操作流程

 2. 内存模型

2. 指针变量的值传递

1. 操作流程

2. 内存模型 

3. 总结 

三:String(值传递)

1. string 标准库 

 2. string 标准库 (常用函数)

3. 字符串String与内存与指针

总结与对比

关键注意事项

四: 结构体


一:函数基础

1. 函数定义

在 C语言 中,函数 是程序的基本执行单元,用于封装一段完成特定任务的代码。它类似于数学中的函数,接收输入(参数),执行操作,并返回输出(结果)使之做到高内聚,低耦合。

  • 内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事,它描述的是模块内的功能联系;

  • 耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。

2. 函数的作用

作用说明
模块化将复杂任务拆分为多个小函数,提高代码可读性
代码复用重复功能只需调用函数,避免重复编写代码
易于维护修改功能只需修改函数内部,不影响其他代码
抽象封装隐藏实现细节,调用者只需关注输入输出

3. 函数格式

function_name:函数名,必须符号标识符的规范,C语言不支持函数的重载,函数名不能重复,函数名是唯一标识符

parameter list:可以为任意C语言能识别的类型,只能当前函数自己使用,可以没有参数列表不写或者写void没有参数列表

function_body:函数体就是程序员自己实现的业务逻辑

return value:函数返回值必须和return_type保持一致,没有返回值类型,不写return value(不能返回任何东西)

有参数:传递参数的时候,参数类型、顺序、个数必须一致

// 定义格式
返回值类型 函数名(参数列表) {// 函数体return 返回值; // 如果返回值类型非void
}// 示例:加法函数
int add(int a, int b) { // 参数为两个int,返回intreturn a + b;
}

 4. 函数形式与使用

可以分为:以下形式:

类型说明
无参无返回值void func(void) { ... }
有参有返回值int max(int a, int b) { ... }
递归函数函数内部调用自身(如计算阶乘)

5. 函数定义的注意事项 

参数传递:

  • 形参 vs 实参

    • 形参:函数定义时声明的参数(如 int a, int b)。

    • 实参:调用函数时传入的具体值(如 add(3,5) 中的 3 和 5)。

  • 传递方式:(下节课重点)

    • 值传递:默认方式,函数内修改形参不影响实参。

    • 地址传递:通过指针传递变量地址,允许修改实参。

// C语言中函数 要先声明再定义
// 1. 有返回值int,但是没有参数列表的函数
int my_function(); // 声明没有函数体// 2. 有返回值,有参数列表
double you_function(int a, char c);// 3. 无返回值,有参数列表声明的时候,可以只写类型,不写参数名
void she_function(char *, int);// 4. 无返回值,无参数列表
void he_function();// 4. 无返回值,无参数列表
void he_function2(void);int main(int argc, char const *argv[])
{// 调用函数int res = my_function(); // 有返回值要接收返回值printf("main中  res=%d\n", res);double d1 = you_function(8, 'p');printf("d1=%lf\n", d1);// 没有返回值,无法接收she_function("yyyy", 9);he_function(888, 9999);he_function2();return 0;
}// 定义了函数
int my_function()
{printf("int my_function().......\n");return 9527;
}double you_function(int a, char c)
{printf("you_function: a=%d,c=%c\n", a, c);return 9.9;
}// 无返回值,有参数列表,定义的时候必须写参数类型和参数名
void she_function(char *c, int a)
{printf("she_function: a=%d,c=%s\n", a, c);return; // 没有返回值类型,不能返回任何内容
}// 无返回值,无参数列表
void he_function()
{// 可以传参,但是函数无法使用参数printf("he_function....11111\n");
}
// 无返回值,无参数列表
void he_function2(void)
{// 不能传递任何参数printf("he_function2....2222\n");
}

二:形参实参

1. 实参实参

形式参数(形参):

就是形式上的参数,没有确定值,函数定义当中出现的参数列表,称为形参max(float a,char b,char* c );

实际参数(实参):

就是实际存在的,已经确定的参数,常量、变量、表达式都是实参,函数调用的时候传递的值,称为实参 max(1.2,'A',"jsjsj");

形参和实参的区别:

1.形参变量只有被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此形参只有在函数内部有效。函数调用结束返回主调函数后则不能继续使用该形参变量;

2.形参出现在函数定义中,在整个函数体内部都可以使用,离开该函数则不能使用;(局限于函数体内

3.形参没有确定的值;(自定义值

4.实参定义后就会分配内存;

5.实参出现在主调函数中,进入被调函数后,实参变量也不能使用(局限于main函数内

6.实参在值传递给形参的时候,必须要有确定的数值(传递前必须初始化

总结:

 1.实参不会随着形参的变化而变化,因为默认是值传递。

 2.实参传递给形参的参数个数、类型、顺序都应相同(传递值相匹配

 3.如果实参是数组名,那么形参传递的是地址的值。(之前讲array有讲过

#include <stdio.h>// 函数声明,函数的参数列表就是形参列表
int add(int, double, char);int main(int argc, char const *argv[])
{// 调用函数// 准备实参int i = 6;double d = 7.7;char c1 = 'U';printf("调用前实参:i=%d,d=%lf,c1=%c\n", i, d, c1);// 调用的时候默认值拷贝int res = add(i, d, c1);printf("res=%d\n", res);printf("调用后实参:i=%d,d=%lf,c1=%c\n", i, d, c1);return 0;
}// 函数定义,函数的参数列表就是形参列表
int add(int a, double b, char c)
{// 形参的值 是有实参进行只拷贝,实参将值赋值给形参,所以形参的改变不会影响实参a = 110;printf("a=%d,b=%lf,c=%c\n", a, b, c);return 8848;
}

2.  值传递

1. 普通变量值传递

在 C 语言中,所有函数参数的传递方式本质都是 值传递(Value Passing)。
但通过传递 指针的值(即内存地址),可以实现类似“地址传递”的效果,从而修改原始数据。

1. 操作流程
  1. 调用函数时

    • 将 实参的值 复制一份,传递给函数的 形参

    • 形参和实参是 两个独立的内存空间

  2. 函数内操作

    • 修改形参的值 不会影响实参,因为它们位于不同内存地址。

  3. 内存模型示例(以交换函数为例):

  • 关键点
    a 和 b 是 x 和 y 的独立副本,修改 a 和 b 不会影响 x 和 y

void swap(int a, int b) {int temp = a;a = b;b = temp; // 仅交换形参a和b的值
}int main() {int x = 3, y = 5;swap(x, y); // 实参x和y的值被复制给a和bprintf("%d %d\n", x, y); // 输出3 5(未交换)
}
 2. 内存模型
栈帧示意图(调用swap时):
高地址
+----------------+
| main的局部变量y=5 |
+----------------+
| main的局部变量x=3 |
+----------------+
| 返回地址         |
+----------------+
| 旧EBP           | ← main的栈帧基址
+----------------+
| swap形参b=5     | ← EBP + 12
+----------------+
| swap形参a=3     | ← EBP + 8
+----------------+
| swap局部变量temp | ← EBP - 4
+----------------+
低地址

2. 指针变量的值传递

1. 操作流程

本质仍是值传递,但传递的是地址

  1. 调用函数时

    • 将 实参的地址(指针的值)复制一份,传递给函数的 指针形参

    • 形参指针和实参指针指向 同一块内存地址

  2. 函数内操作

    • 通过 解引用*)修改指针指向的内存值,直接影响原始数据。

  3. 内存模型示例(正确交换函数):

  • 关键点
    a 和 b 存储的是 x 和 y 的地址,通过 *a 和 *b 直接操作 x 和 y 的内存。

void swap(int *a, int *b) {int temp = *a;*a = *b;       // 修改a指向的内存值*b = temp;     // 修改b指向的内存值
}int main() {int x = 3, y = 5;swap(&x, &y);  // 传递x和y的地址printf("%d %d\n", x, y); // 输出5 3(成功交换)
}
2. 内存模型 
栈帧示意图(调用swap时):
高地址
+----------------+
| main的局部变量y=5 | 地址:0x1004
+----------------+
| main的局部变量x=3 | 地址:0x1000
+----------------+
| 返回地址         |
+----------------+
| 旧EBP           | ← main的栈帧基址
+----------------+
| swap形参b=0x1004| ← 存储y的地址(指针的值)
+----------------+
| swap形参a=0x1000| ← 存储x的地址(指针的值)
+----------------+
| swap局部变量temp | ← EBP - 4
+----------------+
低地址

3. 总结 

底层对比总结

特性值传递地址传递
传递内容变量值的副本变量地址的副本(指针的值)
内存占用形参和实参占用不同内存空间形参指针和实参指针占用不同空间,但指向同一地址
函数内修改效果不影响实参通过指针解引用修改实参
适用场景不需要修改实参的简单数据(如 intchar需要修改实参或传递大数据(如数组、结构体)
  • 值传递:传递数据的副本,函数内操作不影响原始数据。

  • 地址传递:传递数据的地址,通过指针解引用修改原始数据。

  • 底层本质:C语言只有值传递,“地址传递”是通过传递指针值实现的变通方式。

  • 应用场景

    • 简单数据(如基本类型)用值传递。

    • 复杂数据(如结构体、数组)或需修改实参时用地址传递。

三:String(值传递)

1. string 标准库 

#include <string.h>

string .h 头文件定义了一个变量类型、一个宏和各种操作字符数组的函数。

<string.h> 是 C 标准库中的一个头文件,提供了一组用于处理字符串和内存块的函数。这些函数涵盖了字符串复制、连接、比较、搜索和内存操作等

此链接是标准库内的详细教程

C 标准库 – <string.h> | 菜鸟教程

 2. string 标准库 (常用函数)

1. int strcmp(const char *str1, const char *str2)

         把 str1 所指向的字符串和 str2 所指向的字符串进行比较

2. int strncmp(const char *str1, const char *str2, size_t n)

         把 str1 和 str2 进行比较,最多比较前 n 个字节

3. char *strstr(const char *haystack, const char *needle)

        在字符串 haystack 中查找第一次出现字符串 needle(不包含空结束字符)的位置。

4. char *strrchr(const char *str, int c)

        在参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置。

5. char *strcat(char *dest, const char *src)

        把 src 所指向的字符串追加到 dest 所指向的字符串的结尾

6. char *strncat(char *dest, const char *src, size_t n)

        把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止。

7.  char *strcpy(char *dest, const char *src)

        把 src 所指向的字符串复制到 dest。

8. char *strncpy(char *dest, const char *src, size_t n)

        把 src 所指向的字符串复制到 dest,最多复制 n 个字符。

#include <stdio.h>
#include <string.h>
#define LENGTH(array) (sizeof(array) / sizeof(array[0]))int main(int argc, char const *argv[])
{/*char *strcat(char *dest, const char *src)把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。char *strncat(char *dest, const char *src, size_t n)把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止。char *strcpy(char *dest, const char *src)把 src 所指向的字符串复制到 dest。char *strncpy(char *dest, const char *src, size_t n)把 src 所指向的字符串复制到 dest,最多复制 n 个字符。*/char cs1[] = "herong is  teacher!";char cs2[15] = "day day up";// strcat(cs2,cs1);//溢出int n = LENGTH(cs2) - strlen(cs2) - 1;strncat(cs2, cs1, n);// strcpy(cs2,cs1);strncpy(cs2, cs1, LENGTH(cs2) - 1);puts(cs2);puts(cs1);/*int strcmp(const char *str1, const char *str2)把 str1 所指向的字符串和 str2 所指向的字符串进行比较。int strncmp(const char *str1, const char *str2, size_t n)把 str1 和 str2 进行比较,最多比较前 n 个字节。*/char cs3[] = "abce";char cs4[] = "aece";int res=strcmp(cs3,cs4);//0两个字符串相等,正整数前面大于后面,负正数前面小于后面,ascii比较printf("res=%d\n",res);/*char *strstr(const char *haystack, const char *needle)在字符串 haystack 中查找第一次出现字符串 needle(不包含空结束字符)的位置。*/char * cs5=strstr("abcdefghigh","gh");puts(cs5);/*char *strrchr(const char *str, int c)在参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置。*/char* cs6=strrchr("abcdefghighxxx",'g');puts(cs6);return 0;
}

3. 字符串String与内存与指针

C语言没有内置字符串类型,而是通过 字符数组 或 字符指针 表示字符串。字符串以空字符 '\0' 结尾,标志字符串结束。

总结与对比

特性字符数组字符指针(字面量)动态分配字符串
内存区域栈或全局区只读数据段(.rodata)堆(Heap)
可修改性
生命周期自动管理(栈)或程序运行期(全局)程序运行期手动管理(malloc/free)
声明示例char str[] = "Hello";const char *ptr = "Hello";char *s = malloc(6);
内存释放自动(栈)或无需释放(全局)无需释放需调用 free(s)

关键注意事项

  1. 字符串结束符:始终确保字符串以 '\0' 结尾。

  2. 内存越界:操作字符串时避免超出分配的内存空间。

  3. 修改字面量:禁止通过指针修改字符串字面量。

  4. 动态内存管理:配对使用 malloc 和 free,防止泄漏。

#include <stdio.h>
#include <string.h>
#define LENGTH(array) (sizeof(array) / sizeof(array[0]))int main(int argc, char const *argv[])
{//局部变量,局部数组char cs7[]={'a','b','c','\0'};//存在栈中//指针变量,局部变量,在栈中 char* cs8="abc";//"abc"在常量区printf("cs7的地址:%p,cs8的值:%p,&cs8的地址:%p\n",cs7,cs8,&cs8);//局部变量,局部数组 cs9中char cs9[]="abc";//栈中{'a','b','c','\0'},复制的常量区的“abc”的值char* cs10="abc";printf("cs10的值%p,&cp10:%p\n",cs10,&cs10);return 0;
}

 

四: 结构体

C语言结构体:

    1. C数组允许定义可存储相同类型数据的变量,结构是C编程中另一种用户自定义的可用的数据类型,它运行程序员存储不同类型的数据项。

   2. 结构体中的数据成员可以是基本数据类型(比如char\float\double\int...),也可以是其他结构体类型、指针类型等。

   3.  结构用于表示一条记录,比如要存储图书馆中书本的动态:

title  author subject book_id等。

结构体:

    1. 定义由关键字stuct和结构体名称组成,结构体的名称由程序员自定义,符合标识符的命名规范。

结构体:
    1. 结构体是一个数据类型,可以包含不同类型的数据。
    2. 结构体的定义:struct 结构体名 { 数据类型 成员1; 数据类型 成员2; ... };
    3. 结构体的使用:struct 结构体名 变量名; 变量名.成员名 = 值;
    4. 结构体的指针:struct 结构体名 *指针名; 指针名->成员名 = 值;
    5. 结构体的数组:struct 结构体名 数组名[大小]; 数组名[下标].成员名 = 值;
    6. 结构体的数据成员由基本数据类型,指针,数组,结构体等组成。
    7. 结构体的大小:sizeof(结构体名) = 所有成员的大小之和 + 对齐补齐。

    结构体用于表示一段记录:,比如储存图书馆中书本的动态信息。
    结构体定义有关键字struck和结构体名称name组成,结构体的名称由程序员自定义,符合标识符的命名规范

    注意:
    结构成员不能在定义的时候初始化,结构体变量定义后可以初始化。
    结构体中不允许定义函数,结构体中可以定义指针。

 

#include <stdio.h>
/**/
struct student
{int id;char name;char address[20];int gender; // 性别
}// 第二种定义结构体的方法
STRUCK BOOK
{int id;          // 书本编号char name[20];   // 书本名称char author[20]; // 作者int price;       // 价格
}// 3.在函数参数列表·中使用结构体类型的参数
void print_student(struct student s);
void print_student(struct student s)
{char *str = s.gender ? "男" : "女";printf("id=%d,姓名=%s,地址=%s,性别=%s\n", s.s_id, s.s_name, s.address, str);
}b1 = {2222, "C语言程序设计", "谭浩强", 50};int main(int argc, char const *argv[])
{// 声明一个结构体类型的变量,并赋值,赋值顺序必须和结构体定义的一样struct student s1 = {1001, '张三', "北京", 0};// 1. 通过结构体示例,属性名,调用printf("s1.id=%d\n", s1.id);           // 输出s1的idprintf("s1.name=%c\n", s1.name);       // 输出s1的nameprintf("s1.address=%s\n", s1.address); // 输出s1的addressprintf("s1.address=%s\n", s1.gender);  // 输出s1的gender// 2. 调用第二种定义结构体的方法printf("B1.id=%d\n", b1.id);       // 输出B1的idprintf("B1.name=%s\n", b1.name);   // 输出B1的nameprintf("B1.name=%s\n", b1.author); // 输出B1的nameprintf("B1.name=%s\n", b1.name);   // 输出B1的name// 3.在函数参数列表·中使用结构体类型的参数print_student(s1); // 调用函数,传入结构体类型的参数s1struct student s2 = {1002, '李四', "上海", 1};struct student s3 = {1003, '王五', "广州", 0};return 0;
}

http://www.xdnf.cn/news/193915.html

相关文章:

  • Golang|使用函数作为参数和使用接口的联系
  • 23种设计模式
  • STM32N6570-DK ISP调试
  • UDP 报文结构与注意事项总结
  • 每日c/c++题 备战蓝桥杯(P1093 [NOIP 2007 普及组] 奖学金)
  • 勘破养生伪常识,开启科学养生新篇
  • 发那科机器人(基本操作、坐标系、I/O通信)
  • JVM——引入
  • STM32裸机编程架构与思路
  • LangChain4j +DeepSeek大模型应用开发——2 接入其他大模型
  • 练习普通话,说话更有节奏
  • Odoo 18 中计划、待办、项目管理模块解析
  • re题(49)BUUCTF-crackMe
  • 【深度剖析】贵州茅台的数字化转型(2025)(中1)
  • Spring的BeanFactory和FactoryBean的区别
  • springboot dev process
  • JVM模型、GC、OOM定位
  • [250428] Nginx 1.28.0 发布:性能优化、安全增强及新特性
  • wps批注线条怎么取消去掉wps批注后有竖线
  • CentOS7——Docker部署java服务
  • 基于常微分方程的神经网络(Neural ODE)
  • 如何通过Google Chrome增强网页内容的安全性
  • 低空经济无人机创新实训室解决方案
  • 亚马逊环保标识运营指南:抢占流量新赛道的6大策略解析
  • 读论文《Deep learning-assited pulsed discharge plasma catalysis modeling》2023 ECM
  • Android Studio 2024版,前进返回按钮丢失解决
  • springboot项目之websocket的坑:spring整合websocket后进行单元测试后报错的解决方案
  • Qt6.8.2中WebAssembly沙盒环境中预加载文件
  • Cursor
  • 可视化图解算法:合并二叉树