zoukankan      html  css  js  c++  java
  • C语言学习笔记之 函数和指针

    函数和指针

    函数

    函数分类

    • 系统函数,即库函数
      由编译系统提供
    • 用户定义函数
      用以解决用户需要

    函数的作用

    省去重复代码
    一段代码指令为相同功能服务,可以将这段代码定义成函数
    函数可以让程序模块化,便于阅读和修改

    函数的定义

    返回值类型 函数名(参数列表)
    {
        函数体;
    }
    

    返回类型:用户需要函数返回的数据类型(void表示没有返回
    函数名代表的是函数的入口地址

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <stdlib.h>
    
    void test()
    {
    	;
    }
    
    int main() {
    	printf("%p
    ", test);
    
    	return 0;
    }
    

    运行结果:

    00441271
    

    参数列表:用户将函数外部数据传入函数内部的 局部变量
    函数体:函数功能的实现代码

    参数列表(形参)

    形参在函数定义时没有空间,只有在调用函数的时候形参才有空间
    注意!在定义函数时不要给函数赋值!因为形参在函数没有被调用的时候没有空间

    形参格式:

    类型名 + 变量名
    

    例:

    void test(int a, char s[]) {
        puts(s);
        printf("%d",a);
    }
    

    形参的本质是局部变量,当函数执行完毕,形参会被释放

    函数的声明

    函数名(形参列表);
    

    函数名是一个地址

    自动识别

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    
    void test(char s[])
    {
    	puts(s);
    }
    
    int main() {
    	test("Hello world
    "); // 自动识别
    
    	return 0;
    }
    

    显示声明

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    
    int main() {
    	void test(); // 显示声明
    	test("Hello world
    "); // 调用
    
    	return 0;
    }
    
    void test(char s[])
    {
    	puts(s);
    }
    

    如果省略函数的返回类型,会默认返回int

    分文件编程

    在开发中可以将自定义函数与主文件(含主函数)分开,写在不同的文件中,通过头文件(.h)来连接

    例:
    项目结构:

    func.h(函数声明):

    #pragma once // 防止头文件重复包含
    #ifndef _FUNC_H_
    #define _FUNC_H_ // _头文件名大写_H_
    
    double average(int x, int y); // 函数声明
    
    #endif
    

    func.c(函数实现):

    double average(int x, int y) // 函数的定义
    {
    	return ((double)(x + y)) / 2.0;
    }
    

    main.c(主文件):

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include "func.h" // 自定义头文件
    
    int main() {
    	int x = 10, y = 8;
    	printf("%g
    ", average(x, y));
    
    	return 0;
    }
    

    编译:

    gcc func.c main.c -o main.exe
    

    运行结果:

    9
    

    return与exit

    return

    return 返回值;
    

    也可以这样写:

    return;
    

    当执行到return语句,会结束函数,继续从函数调用下一处执行

    exit

    exit(退出状态);
    

    exit会结束进程
    退出状态:

    • 0:正常退出
    • 1:发生错误,强制退出

    递归

    函数自己调用自己,递归必须有出口,否则会导致内存泄漏

    // 例:汉诺塔
    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    
    void hannuota(int n, char a, char b, char c)
    {
    	if (n == 1) // 出口
    	{
    		printf("%c --> %c
    ", a, c);
    	}
    	else
    	{
    		hannuota(n - 1, a, c, b);
    		printf("%c --> %c
    ", a, c);
    		hannuota(n - 1, b, a, c);
    	}
    }
    
    int main()
    {
    	printf("请输入汉诺塔层数:");
    	int n;
    	scanf("%d", &n);
    	hannuota(n, 'A', 'B', 'C');
    
    	return 0;
    }
    

    运行结果:

    请输入汉诺塔层数:3
    A --> C
    A --> B
    C --> B
    A --> C
    B --> A
    B --> C
    A --> C
    

    指针

    指针是C/C++最强大的武器,但也是最容易出错的东西,指针用好了很强大,没用好可能会导致内存泄漏,最终损坏计算机

    概述

    内存

    • 存储器:计算机组成中,用来存储数据和程序,辅助CPU进行运算处理的主要部分
    • 内存:内部存储器,暂存数据,掉电丢失SRAM、DRAM、DDR、DDR2、DDR3
    • 外存:外部存储器,如硬盘

    内存是沟通CPU与硬盘的桥梁

    • 暂时存放CPU中的运算数据
    • 暂存与硬盘等外存交换的数据

    物理存储器和存储地址空间

    物理存储器:实际存在的具体存储器芯片

    • 主板上装插内存条
    • 显示卡上的显示RAM芯片
    • 各种适配卡上的RAM芯片和ROM芯片

    存储地址空间:对存储器编码的范围。在软件上常说的内存是指这一层含义

    • 编码:对每个物理存储单元(一个字节)分配一个号码
    • 寻址:可以根据分配的号码找到对应的存储单元,完成数据的读写

    内存地址

    • 将内存抽象成一个很大的一堆字符数组
    • 编码就是对内存的每一个字节分配一个32位或64位的编号
    • 这个内存编号称之为内存地址

    内存中的每一个数据都会分配相应的地址,地址是唯一的

    • char:占一个字节分配一个地址
    • int:占四字节,分配四个地址

    指针和指针变量

    通过指针找到存储空间,完成数据读写

    在32位平台,地址编号占4字节

    在64位平台,地址编号占8字节

    也就是说,指针的大小是固定不变的

    指针就是地址,地址就是指针
    指针变量是存放地址的变量

    指针的定义

    语法:

    指向类型 *变量名;
    

    例:

    int *p; // 指向int类型的指针
    // *修饰的变量为指针变量,指针变量名为p,而不是*p
    
    int *(a[]); // 指向数组的指针
    int *a[]; // 指针数组,数组的每一个元素都是指针
    void *a; // 可以指向任何类型
    

    指针的初始化与赋值

    // 初始化
    int a = 0; // int 数据
    int *p = NULL; // 初始化
    
    // 赋值
    p = &a; // 将a的地址赋值给指针p,相当于让p指向a
    

    建议指针初始化为NULL
    NULL的定义:

    #define NULL ((void *)0)
    

    NULL即空指针,指向空,当指针指向的内存空间被释放或是不知道指针该指哪里时,尽量都赋值为NULL,在使用指针前先判断是否为NULL,避免内存泄漏

    指针变量存放的是地址,可以通过改变地址让指针指向另外的数据

    #include <stdio.h>
    
    int main()
    {
    	int a = 0, b = 1;
    
    	int *p = &a; // 指向a
    	printf("p = %p
    &a = %p
    ",p,&a);
    
    	p = &b; // 指向b
    	printf("p = %p
    &b = %p
    ",p,&b);
    
    	return 0;
    }
    

    运行结果:

    p = 0060FE98
    &a = 0060FE98
    p = 0060FE94
    &b = 0060FE94
    

    指针的基础使用

    通过指针变量间接操作数据:
    通过 *指针名 可以访问对应的空间

    #include <stdio.h>
    
    int main()
    {
    	int a = 0;
    	int *p;
    	p = &a;
    
    	// 在使用中,*p等价于a
    	printf("*p = %d
    a = %d
    ",*p,a);
    	*p = 100; // 间接修改数据
    	printf("*p = %d
    a = %d
    ",*p,a);
    
    	printf(">");
    	scanf("%d",p);
    	printf("*p = %d
    a = %d
    ",*p,a);
    
    	++(*p);
    	printf("*p = %d
    a = %d
    ",*p,a);
    
    	return 0;
    }
    

    运行结果:

    *p = 0
    a = 0
    *p = 100
    a = 100
    >237
    *p = 237
    a = 237
    *p = 238
    a = 238
    

    指针的宽度

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    
    int main()
    {
    	int num = 0x01020304;
    	int *p = &num;
    	printf("*p = %#x
    ", *p);
    
    	short *p1 = &num;
    	printf("*p1 = %#x
    ", *p1);
    
    	char *p2 = &num;
    	printf("*p2 = %#x
    ", *p2);
    
    	return 0;
    }
    

    运行结果:

    *p = 0x1020304
    *p1 = 0x304
    *p2 = 0x4
    

    解析:

    指针的跨度

    跨度由指向的类型决定

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    
    int main()
    {
    	char *pc = NULL;
    	printf("pc = %#x
    pc + 1 = %#x
    ",pc, (pc + 1));
    
    	short *ps = NULL;
    	printf("ps = %#x
    ps + 1 = %#x
    ", ps, (ps + 1));
    
    	int *pi = NULL;
    	printf("pi = %#x
    pi + 1 = %#x
    ", pi, (pi + 1));
    
    	return 0;
    }
    

    运行结果:

    pc = 0
    pc + 1 = 0x1
    ps = 0
    ps + 1 = 0x2
    pi = 0
    pi + 1 = 0x4
    

    使用实例:

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    
    int main()
    {
    	int a[4] = { 0,1,0,0 };
    	int *p = NULL;
    	p = a;
    	printf("%d", *(p + 1));
    
    	return 0;
    }
    

    运行结果:

    1
    

    const指针

    int num = 0;
    
    const int *p = &num; // *p只读,p可读可写
    *p = 10; // 报错
    p = NULL; // 没问题
    
    int * const p2 = &num; // *p2可读可写,p2只读
    *p2 = 10; // 没问题
    p2 = NULL; // 报错
    
    const int * const p3 = &num; // *p3只读,p3只读
    

    指向同一数组的两个指针间的关系

    1. 指向同一数组的两个指针变量相减,是两个指针变量间元素的个数
    2. 指向同一数组的两个指针变量相加无意义
    3. 指向同一数组的两个指针变量 p1 > p2,p1 == p2等,表示两个指针间的位置关系
    4. 指向同一数组的两个指针变量 p1 = p2,指向同一位置

    注意事项

    1. 不要操作未初始化的指针变量,建议将指针变量初始化为NULL
    2. 不要操作NULL指针,在使用指针前先判断是否为NULL
    3. 小心野指针和悬空指针
    4. 不要操作自定义地址
  • 相关阅读:
    脏读 幻读 不可重复读
    按位与、或、异或等运算方法
    java适配器模式
    servlet/filter/listener/interceptor区别与联系
    Struts2、SpringMVC、Servlet(Jsp)性能对比 测试
    Struts2的优点与Struts1的区别:
    ITOO 第一个任务,新建界面
    导出word使用模版
    【Web前端】---js调用本地应用程序
    JQuery经典小例子——可编辑的表格
  • 原文地址:https://www.cnblogs.com/zhujiangyu/p/13620017.html
Copyright © 2011-2022 走看看