C进阶4

▲二维数组参数中第一维的参数可以省略

void f(int a[5])<==>void f(int a[])<==>void f(int* a)
void g(int a[3][3])<==>void g(int a[][3])<==> void g(int (*a)[3])

▲等价关系:

一位数组float a[5]   指针float* a
指针数组int* a[5]    指针的指针int** a
二维数组char a[3][4] 数组的指针char (*a)[4]


C语言中无法向一个函数传递任意的多维数组
必须提供第一维之外的所有维长度;第一维之外的维度信息用于完成指针运算;
N维数组的本质是一维数组,元素是N—1维的数组;对于多维数组只有第一维信息可变。

#include <stdio.h>
void access(int a[][3], int row)
{
    int col = sizeof(*a) / sizeof(int);
    int i = 0;
    int j = 0;
    
    printf("sizeof(a) = %d\n", sizeof(a));
    printf("sizeof(*a) = %d\n", sizeof(*a));
    
    for(i=0; i<row; i++)
    {
        for(j=0; j<col; j++)
        {
            printf("%d\n", a[i][j]);
        }
    }
    
    printf("\n");
}

void access_ex(int b[][2][3], int n)
{
    int i = 0;
    int j = 0;
    int k = 0;
    
    printf("sizeof(b) = %d\n", sizeof(b));
    printf("sizeof(*b) = %d\n", sizeof(*b));
    
    for(i=0; i<n; i++)
    {
        for(j=0; j<2; j++)
        {
            for(k=0; k<3; k++)
            {
                printf("%d\n", b[i][j][k]);
            }
        }
    }
    
    printf("\n");
}

int main()
{
    int a[3][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};
    int aa[2][2] = {0};
    int b[1][2][3] = {0};
    
    access(a, 3);
    access(aa, 2);
    access_ex(b, 1);
    access_ex(aa, 2);
    
    return 0;
}

//------------------------------------------------
如何使用C语言直接跳转到某个固定的地址开始执行?
通过函数指针。
C语言中函数有自己特定的类型,由返回值、参数类型和参数个数决定。

int add(int i,int j)的类型为int (int,int)

C语言中通过typede为函数类型重命名

typedef type name(parameter list)
例:typedef int f(int,int);
    typedef void p(int);

#include <stdio.h>

typedef int(FUNC)(int);

int test(int i)
{
    return i * i;
}

void f()
{
    printf("Call f()...\n");
}

int main()
{
    FUNC* pt = test;
    void(*pf)() = &f;
    
    printf("pf = %p\n", pf);
    printf("f = %p\n", f);
    printf("&f = %p\n", &f);
    
    pf();
    
    (*pf)();
    
    printf("Function pointer call: %d\n", pt(2));
    
    return 0;
}

回调函数是利用函数指针实现的一种调用机制。

函数指针用于指向一个函数,函数名是执行函数体的入口地址;可通过
函数类型定义函数指针:
FuncType* pointer;也可以type (*pointer)(parameter list);
pointer为函数指针变量名,type为所指函数的返回值类型,parameter list为所指函数的参数类型列表。

#include <stdio.h>

typedef int(*Weapon)(int);

void fight(Weapon wp, int arg)
{
    int result = 0;
    
    printf("Fight boss!\n");
    
    result = wp(arg);
    
    printf("Boss loss: %d\n", result);
}

int knife(int n)
{
    int ret = 0;
    int i = 0;
    
    for(i=0; i<n; i++)
    {
        printf("Knife attack: %d\n", 1);
        ret++;
    }
    
    return ret;
}

int sword(int n)
{
    int ret = 0;
    int i = 0;
    
    for(i=0; i<n; i++)
    {
        printf("Sword attack: %d\n", 5);
        ret += 5;
    }
    
    return ret;
}

int gun(int n)
{
    int ret = 0;
    int i = 0;
    
    for(i=0; i<n; i++)
    {
        printf("Gun attack: %d\n", 10);
        ret += 10;
    }
    
    return ret;
}

int main()
{
    fight(knife, 3);
    fight(sword, 4);
    fight(gun, 5);
    
    return 0;
}

//----------------------------------------------

int (*p)(int):p为指针,指向函数,指向的函数有一个int参数,返回值为int。

int (*p1)(int*,int(*f)(int*)):p1为指针,指向函数;指向的函数有int*,f为第二个参数,它是函数指针,
指向的参数时int*,返回值是int,返回值是int。
int (*p2[5])(int*):p2为数组,有5个元素,这5个元素为指针,指向函数,函数类型为int(int*)。
int (*(*p3)[5])(int*):p3为指针,数组指针,指向的数组有5个元素,这5个元素为指针,该指针是函数指针,指向的函数类型为int(int*)
int* (*(*p4)(int*))(int*):p4为指针,函数指针,参数为int*,返回值为指针,函数指针,指向的函数类型int*(int*)
int (*(*p5)(int*))[5]:p5为指针,指向函数,函数指针参数为int*,返回值为指针,指向数组,指向的数组类型为int[5]

typedef int(ArrayType)[5];
typedef ArrayType*(FuncType)(int*);
FunType* p5;

//------------------------------------------
动态内存分配
malloc0将返回什么?合法。
malloc0不释放后果?产生内存泄漏。
//-------------------------------------------
堆和栈和静态存储区
栈:主要用于函数调用的使用,不要返回局部变量的地址。
堆:内存的动态申请和归还;堆中被程序申请使用的内存在被主动释放前将一直有效。
静态存储区:全局变量和静态变量
为什么有了栈还需要堆?答:栈上的数据在函数返回后就会被释放掉,无法传递到函数外部,如局部数组。
注:空闲链表法、位图法、对象池法。malloc free。

#include <stdio.h>

int g_v = 1;

static int g_vs  = 2;

void f()
{
    static int g_vl = 3;
    
    printf("%p\n", &g_vl);
}

int main()
{
    printf("%p\n", &g_v);
    
    printf("%p\n", &g_vs);
    
    f();
    
    return 0;
}

注:3个变量的地址是挨着的。
//-------------------------------------------------
程序的内存布局:
在这里插入图片描述
▲程序和进程不同:
程序是静态的概念,表现形式为一个可执行文件
进程是动态的概念,程序由操作系统加载运行后得到进程
每个程序可以对应多个进程;每个进程只能对应一个程序。
▲包含脚本代码的文本文件是一种类型的可执行程序吗?如果是,对应什么样的进程呢?
在这里插入图片描述
▲堆栈段在程序运行后才正式存在,是程序运行的基础

.bss段存放的是未初始化的全局变量和静态变量
.text段存放的是程序中的可执行代码
.data段保存的是已经初始化了的全局变量和静态变量
.rodata段存放程序中的常量值,如字符串常量


静态存储区通常指程序中的.bss和.data段
只读存储区通常指程序中的.rodata段
局部变量所占空间为栈上的空间
动态空间为堆中的空间
程序可执行代码存放于.text段

问:同是全局变量和静态变量,为什么初始化的和未初始化的保存在不同段中?

C规定,未初始化变量的初值为0,这个清0的操作是由启动代码完成的,还有已初始化变量的初值的设置,也是由启动代
码完成的。为了启动代码的简单化,编译链接器会把已初始化的变量放在同一个段:.data,这个段的映像(包含了各个
变量的初值)保存在“只读数据段”,这样启动代码就可以简单地复制这个映像到 .data 段,所有的已初始化变量就都初
始化了。而未初始化变量也放在同一个段:.bss,启动代码简单地调用 memset 就可以把所有未初始化变量都清0。

//-----------------------------------------------------
内存操作经典问题:
野指针的由来:局部指针变量没有初始化;指针所指向的变量在指针之前被销毁;
使用已经释放过得指针;进行了错误的指针运算;进行了错误的强制类型转换;
基本原则:
绝不返回局部变量和局部数组的地址;任何变量在定义后必须0初始化;
字符数组必须确认0结束符后才能成为字符串;任何使用与内存操作相关的函数必须指定长度信息;

#include <stdio.h>
#include <malloc.h>


int main()
{
    int* p1 = (int*)malloc(40);
    int* p2 = (int*)1234567;      //error
    int i = 0;
    
    for(i=0; i<40; i++)
    {
        *(p1 + i) = 40 - i;
    }

    free(p1);    //并不会将指针置为空
    
    for(i=0; i<40; i++)
    {
        p1[i] = p2[i];  // 使用已经释放了的内存空间
    }
    
    return 0;
}

常见内存错误:

结构体成员指针未初始化;结构体成员指针未分配足够的内存;内存分配成功,但并未初始化;内存操作越界;
内存错误的本质源于指针保存的地址为非法值;指针变量未初始化,保存随机值;指针运算导致内存越界;
内存泄漏源于malloc和free不匹配;

//-----------------------------------------------
函数:
函数的参数在栈上分配空间;函数的实参并没有固定的计算次序;顺序点是C语言中变量修改的最晚时机;
C语言中可以定义参数可变的函数:参数可变函数的实现依赖于stdarg.h头文件
va_list参数集合;va_start标识参数访问的开始;va_end标识参数访问的结束;
●可变参数必须从头到尾按照顺序逐个访问,参数列表中至少要存在一个确定的命名参数,可变参数函数无法确定实际存在的参数的数量,
可变参数函数无法确定参数的实际类型;
注:va_arg中如果指定了错误的类型,结果不可预测。
调用约定指定了函数参数的入栈顺序以及栈的清理方式;可变参数必须顺序的访问,无法直接访问中间的参数值;

#include <stdio.h>
#include <stdarg.h>

float average(int n, ...)
{
    va_list args;
    int i = 0;
    float sum = 0;
    
    va_start(args, n);
    
    for(i=0; i<n; i++)
    {
        sum += va_arg(args, int);
    }
    
    va_end(args);
    
    return sum / n;
}

int main()
{
    printf("%f\n", average(5, 1, 2, 3, 4, 5));
    printf("%f\n", average(4, 1, 2, 3, 4));
    
    return 0;
}

//----------------------------------------------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值