C语言 12.一些知识点

1宏函数

1.1#define MYADD(x,y)((x) + (y)

1.2将一些频繁短小的函数 写成宏函数

1.3宏函数优点:以空间换时间

1.4普通函数有入栈、出栈时间开销


#define MYADD(x,y)  x+y
#define MYADD2(x,y)  ((x)+(y))
/*
1. 宏函数需要加小括号修饰,保证运算的完成性质
2.通常会将频繁、短小的函数写成宏函数
3.宏函数会比普通函数在一定程度上效率高,省去普通函数入栈出栈时间上的开销
优点:以空间换时间
*/
void test01()
{
    printf("%d\n", MYADD(10, 20));   // 30
    printf("%d\n", MYADD(10, 20)*20);   // 10+20*20
    printf("%d\n", MYADD2(10, 20) * 20);   // 600
}

2函数调用流程

2.1局部变量、函数形参、函数返回地址… 入栈 和 出栈
在这里插入图片描述

主调函数,从右向左

3调用惯例

3.1主调函数和被调函数必须要有一致约定,才能正确的调用函数,这个约定我们称为调用惯例

3.2调用惯例 包含内容: 出栈方、参数传递顺序、函数名称修饰

3.3C/C++下默认调用惯例: cdecl 从右到左,主调函数管理出栈

4栈的生长方向和内存存放方向

4.1栈生长方向

4.1.1栈底 — 高地址

4.1.2栈顶 — 低地址

4.2内存存放方向

4.2.1高位字节数据 — 高地址

4.2.2低位字节数据 — 低地址

4.2.3小端对齐方式(高高低低)
在这里插入图片描述


/*
1.栈的生长方向
*/
void test01()
{
    int a = 10;         // 栈底   高地址
    int b = 10;
    int c = 10;
    int d = 10;         // 栈顶   低地址
    printf("%d\n", &a);
    printf("%d\n", &b);
    printf("%d\n", &c);
    printf("%d\n", &d);
    //-1015613548         32字节,因为还需要记录上下文,所以不止4个字节
    //    - 1015613516
    //    - 1015613484
    //    - 1015613452
}
/*
2.内存的存放方向
*/
void test02()
{
    int a = 0x11223344;
    char* p = &a;
    printf("%x\n", *p); // 44  低位字节数据      低地址
    printf("%x\n", *(p+1)); // 33  高位字节数据  高地址
}

5空指针和野指针

5.1空指针

5.1.1不能向NULL或者非法内存拷贝数据

5.2野指针

5.2.1指针变量未初始化

5.2.2指针释放后未置空

5.2.3指针操作超越变量作用域

5.3空指针可以重复释放、野指针不可以重复释放



/*
1.不能将NULL或者非法的内存拷贝数据
*/
//void tese01()
//{
//    char* p = NULL;
//    // 给p指向的内存拷贝内容
//    strcpy(p, "1111");     //err
//
//    char* q = 0x1122;
//    // 给q指向的内存区域拷贝内容
//    strcpy(q, "2222");      // err
//}
/* 3.指针超越变量作用域*/
int* doWork()
{
    int a = 10;
    int* p = &a;
    return p;
}

/*
2.野指针出现的情况
*/
void test02()
{
     指针变量未初始化
    //int* p;        // 到这一步可以编译可以运行
    //printf("%d\n", *p); // 到这一步会报错,未初始化的变量
    // 指针释放后未置空
    char* str = malloc(100);
    free(str);
    // 记住释放后置空,防止野指针出现
    //str = NULL;       // 空指针可以重复释放
    //free(str);      // 野指针不可以重复释放
    int* p = doWork();      //函数调用之后,内存就会释放,指针变成了野指针
    printf("%d\n", *p);    // 这一步应该报错,但是编译器自己优化了一下,所以没有报错
    printf("%d\n", *p);    // 这一步就会报错了
}

6指针的步长

6.1+1之后跳跃的字节数

6.2解引用 解出的字节数

6.3自定义结构体做步长练习

6.3.1通过 offsetof( 结构体名称, 属性)找到属性对应的偏移量

6.3.2offsetof 引入头文件 #include<stddef.h>
在这里插入图片描述

//1、指针的步长代表 指针+1之后跳跃的字节数
void test01()
{
    char * p = NULL;
    printf("%d\n", p);
    printf("%d\n", p+1);
    double * p2 = NULL;
    printf("%d\n", p2);
    printf("%d\n", p2 + 1);
    //  0  4  0   8
}

//2、解引用的时候,解出的字节数量
void test02()
{
    char buf[1024] = { 0 };
    int a = 1000;
    //memcpy(buf , &a, sizeof(int));
    //char* p = buf;
    //printf("%d\n", *(int*)p);
    memcpy(buf + 1, &a, sizeof(int));
    char * p = buf;
    printf("%d\n", *(int *)(p+1));
}
//步长练习,自定义数据类型练习
struct Person
{
    char a;     // 0 ~ 3
    int b;      // 4 ~ 7
    char buf[64]; // 8 ~ 71
    int d;     // 72 ~ 75
};
void test03()
{
    struct Person p = { 'a', 10, "hello world", 20 };
    
    printf("d属性的偏移量: %d\n", offsetof(struct Person, d));  // 利用系统函数来计算
    printf("d属性的值为:%d\n", *(int *)((char *)&p + offsetof(struct Person, d)));
}

7.指针的间接赋值

7.1三大条件

7.1.1一个普通变量+指针变量( 实参+形参)

7.1.2建立关系

7.1.3通过* 操作内存

7.2利用Qt实现 操作地址修改内存
在这里插入图片描述

//间接赋值三大条件
// 一个普通变量  和 指针变量   或  一个实参和一个形参
// 建立关系
// * 操作内存
void changeValue(int *a) // int * a = &a2;  
{
    *a = 1000;
}
void test01()
{
    int  a = 10;
    int * p = NULL;
    p = &a;
    *p = 100;
    // 一个形参和一个实参
    int a2 = 10;
    changeValue(&a2);
    printf("%d\n", a2);   //1000
    printf("%d\n", &a2);  // 这个地址每次都不一样
}

8.指针做函数参数的输入输出特性

8.1输入特性

8.1.1在主调函数中分配内存,被调函数使用

8.1.2分配在栈上和堆区

8.2输出特性

8.2.1在被调函数中分配内存,主调函数使用



//输入特性: 主调函数分配内存,被调函数使用
void func(char * p)
{
    strcpy(p, "hello world");
}
void test01()
{
    //在test01中分配了内存,分配在栈上
    char buf[1024] = { 0 };
    func(buf);
    printf("%s\n", buf);   // helloworld
}
void printString(char * str)
{
    printf("%s\n", str + 6);
}
void test02()
{
    char * p = malloc(sizeof(char)* 64);
    memset(p, 0, 64);
    strcpy(p, "hello world");
    printString(p);      // world
    if (p != NULL)     // 手动开辟,手动释放
    {
        free(p);
        p = NULL;
    }
}
//输出特性:在被调函数中分配内存,主调函数使用
void allocateSpace(char ** pp)
{
    // 在被调函数中分配内存
    char * str = malloc(sizeof(char)* 64);
    memset(str, 0, 64);
    strcpy(str, "helloworld");
    *pp = str;
}
void test03()
{
    char * p = NULL;
    allocateSpace(&p);
    printf("%s\n", p);  // helloworld
}

9.字符串强化训练

9.1字符串结束标志 \0

9.2sizeof 和 strlen

9.3拷贝字符串 利用三种方式

9.3.1利用[]

9.3.2利用指针

9.3.3while (*dest++ = *src++){}

9.4翻转字符串

9.4.1利用[ ]

9.4.2利用指针



void test01()
{
    字符串结束标志位 \0
    //char str1[] = { 'h', 'e', 'l', 'l', 'o' ,'\0'};
    //printf("%s\n", str1);
    // 后面全部初始化为0
    //char str2[100] = { 'h', 'e', 'l', 'l', 'o' };
    //printf("%s\n", str2);         
    //char str3[] = "hello";
    //printf("%s\n", str3);
    //printf("sizeof str:%d\n", sizeof(str3)); //6 统计整个字符串的长度
    //printf("strlen str:%d\n", strlen(str3)); //5  统计字符串长度不包括0
    //char str4[100] = "hello";
    //printf("sizeof str:%d\n", sizeof(str4));  // 100
    //printf("strlen str:%d\n", strlen(str4));  // 5
    //char str5[] = "hello\0world";
    //printf("%s\n", str5);  //  hello
    //printf("sizeof str5:%d\n", sizeof(str5)); //12
    //printf("strlen str5:%d\n", strlen(str5)); //5
    char str6[] = "hello\012world";    
    //  '\012' 是转义字符,\0是把12换成进十六制=10   而10在ASCII中对应的是换行符号
    printf("%s\n", str6);   // hello 换行world
    printf("sizeof str6:%d\n", sizeof(str6)); //12
    printf("strlen str6:%d\n", strlen(str6)); //11
}

//字符串拷贝实现
//1、利用[] 实现
void copyString01(char * dest , char * src)
{
    int len =strlen(src);
    for (int i = 0; i < len;i++)
    {
        dest[i] = src[i];
    }
    dest[len] = '\0';
}
//2、利用字符串指针
void copyString02(char * dest, char * src)
{
    while (*src != '\0')
    {
        *dest = *src;
        dest++;
        src++;
    }
    *dest = '\0';
}
//3
void copyString03(char* dest, char* src)
{
    while (*dest++ = *src++) {}
    // 不断地进入循环,将右边的拷贝到左边并将指针向后面移动一个单位
    // 当遇到\0时,先拷贝,之后括号里判断为\0,跳出循环
}
void test02()
{
    char * str = "hello world";
    
    char buf[1024];
    //copyString01(buf, str);
    //copyString02(buf, str);
    copyString03(buf, str);
    printf("%s\n", buf);
}
//字符串翻转
void reverseString01(char * str)
{
    //利用[]
    int len = strlen(str);
    int start = 0;
    int end = len - 1;
    while (start < end)
    {
        char temp = str[start];
        str[start] = str[end];
        str[end] = temp;
        start++;
        end--;
    }
}
void reverseString02(char * str)
{
    // 用指针
    int len = strlen(str);
    char * start = str;
    char * end = str + len - 1;
    while (start < end)
    {
        char temp = *start;
        *start = *end;
        *end = temp;
        start++;
        end--;
    }
}

10.sprintf使用

10.1格式化字符串

10.2sprintf(目标字符串,格式化内容,占位参数…)

10.3返回值 有效字符串长度


    //  1.格式化字符串
    char buf[1024];
    //memset(buf, 0, 1024);
    //sprintf(buf, "今天 %d 年 %d月  %d日", 2018, 6, 30);
    //printf("%s\n", buf);
    //2. 拼接字符串
    //memset(buf, 0, 1024);
    //char str1[] = "hello";
    //char str2[] = "world";
    //int len = sprintf(buf, "%s%s", str1, str2); //返回值是字符串长度 不包含\0
    //printf("buf:%s len:%d\n", buf, len);
    //3. 数字转字符串
    //memset(buf, 0, 1024);
    //int num = 100;
    //sprintf(buf, "%d", num);
    //printf("buf:%s\n", buf);
    int num = 100;
    //设置宽度 右对齐
    memset(buf, 0, 1024);
    sprintf(buf, "%8d", num);
    printf("buf:%s\n", buf);
    设置宽度 左对齐
    memset(buf, 0, 1024);
    sprintf(buf, "%-8d", num);
    printf("buf:%sa\n", buf);
    //转成16进制字符串 小写
    memset(buf, 0, 1024);
    sprintf(buf, "0x%x", num);
    printf("buf:%s\n", buf);
    //转成8进制字符串
    memset(buf, 0, 1024);
    sprintf(buf, "0%o", num);
    printf("buf:%s\n", buf);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值