01 函数调用流程
宏函数在一定场景下宏函数效率要比函数高,引申处函数调用流程
# include<stdio.h>
#include<string.h>
#include<stdlib.h>
//宏函数,不是一个真正的函数
#define MYADD(x,y) ((x)+(y))
#define MAX 1024
//这个才是真正的函数 返回值 参数 函数体
int add(int a, int b)
{
return a + b;
}
//宏函数在一定场景下宏函数效率要比函数高
int main()
{
int a = 10;
int b = 20;
printf("a + b = %d\n", MYADD(a, b));
}
栈中调用流程如下:
宏函数以空间换时间
# include<stdio.h>
#include<string.h>
#include<stdlib.h>
//宏函数,不是一个真正的函数(只是预处理器进行简单的文本替换)
#define MYADD(x,y) ((x)+(y))
#define MAX 1024
//这个才是真正的函数 返回值 参数 函数体
int add(int a, int b)
{
return a + b;
}
//宏函数在一定场景下宏函数效率要比函数高
int main()
{
int a = 10;
int b = 20;
//以空间换时间
//对于频繁使用,并且短小的函数,我们一般使用宏函数代替,因为宏函数没有普通函数调用的开销(函数压栈,调转,返回等)
printf("a + b = %d\n", ((a)+(b)));
}
02 函数的调用惯例
1.对于上图问题1:
现在,我们大致了解了函数调用的过程,这期间有一个现象,那就是函数的调用者和被调用者对函数调用有着一致的理解,例如,它们双方都一致的认为函数的参数是按照某个固定的方式压入栈中。如果不这样的话,函数将无法正确运行。
如果函数调用方在传递参数的时候先压入a参数,再压入b参数,而被调用函数则认为先压入的是b,后压入的是a,那么被调用函数在使用a,b值时候,就会颠倒。
因此,函数的调用方和被调用方对于函数是如何调用的必须有一个明确的约定,只有双方都遵循同样的约定,函数才能够被正确的调用,这样的约定被称为”调用惯例(Calling Convention)”
事实上,在c语言里,存在着多个调用惯例,而默认的是cdecl.任何一个没有显示指定调用惯例的函数都是默认是cdecl惯例。比如我们上面对于func函数的声明,它的完整写法应该是:
int _cdecl func(int a,int b);
注意: _cdecl不是标准的关键字,在不同的编译器里可能有不同的写法,例如gcc里就不存在_cdecl这样的关键字,而是使用__attribute__((cdecl)).
03 变量传递分析
04 栈的生长方向和内存存放方向
//1. 栈的生长方向
void test01
{
int a = 10;
int b = 20;
int c = 30;
int d = 40;
printf("a = %d\n", &a);
printf("b = %d\n", &b);
printf("c = %d\n", &c);
printf("d = %d\n", &d);
//a的地址大于b的地址,故而生长方向向下
}
//2. 内存生长方向(小端模式)
void test02()
{
//高位字节 -> 地位字节
int num = 0xaabbccdd;
unsigned char* p = #
//从首地址开始的第一个字节
printf("%x\n",*p);
printf("%x\n", *(p + 1));
printf("%x\n", *(p + 2));
printf("%x\n", *(p + 3));
}
05 指针基本概念
指针变量:指针是一种数据类型,占用内存空间,用来保存内存地址。
空指针:标准定义了NULL指针,它作为一个特殊的指针变量,表示不指向任何东西。要使一个指针为NULL,可以给它赋值一个零值。(不允许向NULL和非法地址拷贝内存。)
野指针:指向一个已删除的对象或未申请访问受限内存区域的指针。与空指针不同,野指针无法通过简单地判断是否为 NULL避免,而只能通过养成良好的编程习惯来尽力减少。对野指针进行操作很容易造成程序错误。
什么情况下回导致野指针?
- 指针变量未初始化
- 指针释放后未置空
- 指针操作超越变量作用域
间接访问操作符:通过一个指针访问它所指向的地址的过程叫做间接访问,或者叫解引用指针,这个用于执行间接访问的操作符是*。(注意:对一个int类型指针解引用会产生一个整型值,类似地,对一个float指针解引用会产生了一个float类型的值。)
06 指针的步长
指针是一种数据类型,是指它指向的内存空间的数据类型。指针所指向的内存空间决定了指针的步长。指针的步长指的是,当指针+1时候,移动多少字节单位。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<stddef.h>
//指针步长:指针变量+1 要向后跳多少字节
void test01()
{
char* p = NULL;
printf("%d\n", p);
printf("%d\n", p + 1);
printf("----------\n");
int* p2 = NULL;
printf("%d\n", p2);
printf("%d\n", p2 + 1);
char buf[1024] = { 0 };
int a = 100;
memcpy(buf + 1, &a, sizeof(int));
char* p3 = buf;
printf("*(int*)(p3 + 1)=%d\n", *(int*)(p3 + 1));
}
struct Person
{
int a;
char b;
char buf[64];
int d;
};
void test02()
{
struct Person p = { 10,'a',"hello world!",100 };
char b;
printf("a off:%d\n", offsetof(struct Person, b));
printf("d =%d\n", *(int*)((char*)&p + offsetof(struct Person, d)));
}
int main()
{
test01();
printf("*************");
test02();
return 0;
}
07 指针的间接赋值
间接赋值的推论:
- 用1级指针形参,去间接修改了0级指针(实参)的值。
- 用2级指针形参,去间接修改了1级指针(实参)的值。
- 用3级指针形参,去间接修改了2级指针(实参)的值。
- 用n级指针形参,去间接修改了n-1级指针(实参)的值。
void test(){
int b;
int *q = &b; //0级指针
int **t = &q;
int ***m = &t;
}
08 指针做函数参数的输入特性和输出特性
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//1.主调函数分配内存,被调函数使用内存——指针的输入特性
void printfString(const char* str)
{
printf("打印内容:%s\n", str+2);
}
void printfStringArray(char** arr, int len)
{
//arr[0]是char* 类型的
for (int i = 0; i < len; ++i)
{
printf("%s\n", arr[i]);
}
}
void test01()
{
//堆上分配内存
char* s = malloc(sizeof(char) * 100);
memset(s, 0, 100);
strcpy(s, "I am Polly!");
printfString(s);
//堆上分配
//数组名左函数参数就会退化为指向数组首地元素的指针
char* strs[] = { "aaaa","bbbb","cccc","ddddd" };
int len = sizeof(strs) / sizeof(strs[0]);
printfStringArray(strs, len);
}
//2.输出特性:被调函数分配内存,主调函数使用内存
void allocateSpace(char** temp)
{
char* p = malloc(100);
memset(p, 0, 100);
strcpy(p, "hello world!");
//指针的间接赋值
*temp = p;
}
void test02()
{
char* p = NULL;
allocateSpace(&p);
printf("p=%s\n", p);
if (p != NULL)
{
free(p);
p = NULL;
}
}
int main()
{
test01();
printf("------------\n");
test02();
system("pause");
return EXIT_SUCCESS;
}
09 字符串拷贝
//拷贝方法1
void copy_string01(char* dest, char* source ){
for (int i = 0; source[i] != '\0';i++){
dest[i] = source[i];
}
}
//拷贝方法2
void copy_string02(char* dest, char* source){
while (*source != '\0' /* *source != 0 */){
*dest = *source;
source++;
dest++;
}
}
//拷贝方法3
void copy_string03(char* dest, char* source){
//判断*dest是否为0,0则退出循环
while (*dest++ = *source++){}
}
//拷贝方法4
//1)应该判断下传入的参数是否为NULL
//2)最好不要直接使用形参
int copy_string04(char* dest, char* source){
if (dest == NULL){
return -1;
}
if (source == NULL){
return -2;
}
char* src = source;
char* tar = dest;
while (*tar++ = *src++){}
return 0;
}
10 字符串反转
void reverse_string(char* str){
if (str == NULL){
return;
}
int begin = 0;
int end = strlen(str) - 1;
while (begin < end){
//交换两个字符元素
char temp = str[begin];
str[begin] = str[end];
str[end] = temp;
begin++;
end--;
}
}
void test(){
char str[] = "abcdefghijklmn";
printf("str:%s\n", str);
reverse_string(str);
printf("str:%s\n", str);
}
11 sprintf
#include <stdio.h>
int sprintf(char *str, const char *format, ...);
功能:
根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直到 出现字符串结束符 '\0' 为止。
参数:
str:字符串首地址
format:字符串格式,用法和printf()一样
返回值:
成功:实际格式化的字符个数
失败: - 1
void test(){
//1. 格式化字符串
char buf[1024] = { 0 };
sprintf(buf, "你好,%s,欢迎加入我们!", "John");
printf("buf:%s\n",buf);
memset(buf, 0, 1024);
sprintf(buf, "我今年%d岁了!", 20);
printf("buf:%s\n", buf);
//2. 拼接字符串
memset(buf, 0, 1024);
char str1[] = "hello";
char str2[] = "world";
int len = sprintf(buf,"%s %s",str1,str2);
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);
//设置宽度 右对齐
memset(buf, 0, 1024);
sprintf(buf, "%8d", num);
printf("buf:%s\n", buf);
//设置宽度 左对齐
memset(buf, 0, 1024);
sprintf(buf, "%-8d", num);
printf("buf:%s\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);
}