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. 操作流程
调用函数时:
将 实参的值 复制一份,传递给函数的 形参。
形参和实参是 两个独立的内存空间。
函数内操作:
修改形参的值 不会影响实参,因为它们位于不同内存地址。
内存模型示例(以交换函数为例):
关键点:
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. 操作流程
本质仍是值传递,但传递的是地址
调用函数时:
将 实参的地址(指针的值)复制一份,传递给函数的 指针形参。
形参指针和实参指针指向 同一块内存地址。
函数内操作:
通过 解引用(
*
)修改指针指向的内存值,直接影响原始数据。内存模型示例(正确交换函数):
关键点:
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. 总结
底层对比总结
特性 值传递 地址传递 传递内容 变量值的副本 变量地址的副本(指针的值) 内存占用 形参和实参占用不同内存空间 形参指针和实参指针占用不同空间,但指向同一地址 函数内修改效果 不影响实参 通过指针解引用修改实参 适用场景 不需要修改实参的简单数据(如 int
,char
)需要修改实参或传递大数据(如数组、结构体)
值传递:传递数据的副本,函数内操作不影响原始数据。
地址传递:传递数据的地址,通过指针解引用修改原始数据。
底层本质: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)
关键注意事项
字符串结束符:始终确保字符串以
'\0'
结尾。内存越界:操作字符串时避免超出分配的内存空间。
修改字面量:禁止通过指针修改字符串字面量。
动态内存管理:配对使用
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;
}