C++笔记day15 函数调用等

193 阅读8分钟

宏函数

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

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

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

示例

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

int main(void)
{

	system("pause");
	return EXIT_SUCCESS;
}

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

函数调用流程

局部变量、函数形参、函数返回地址.. 入栈 和 出栈

image.png

调用惯例

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

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

C/C++下默认调用惯例: 
    cdecl:从右到左;函数调用方管理出栈,主调函数管理出栈;名字修饰为下划线+函数名。

函数变量传递分析

main函数在栈区开辟的内存所有子函数均可以使用;

main函数在堆区开辟的内存,所有子函数均可以使用;

子函数1在栈区开辟的内存,子函数1和子函数的子函数均可以使用;
子函数的子函数在全局区开辟的内存,子函数1main均可以使用。

image.png

image.png

image.png

image.png

示例

void func() 
{
	char* p = malloc(10);//堆区数据,只要没有释放,都可以使用
	int c = 10;//在func中可以使用,test01和main中都不可以使用
	return p;
}

void test01() 
{
	int b = 10;//在test01、func可以使用,在main中不可以使用
	func();
}
int main(void)
{
	int a = 10;//在main,test01,func中都可以使用
	system("pause");
	return EXIT_SUCCESS;
}

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

栈生长方向

栈底  ---  高地址
栈顶  ---  低地址

内存存放方向

高位字节数据  ---  高地址
低位字节数据  ---  低地址
这种方式称为小端对齐方式

image.png

示例

//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);
}
//2. 内存的存放方向
void test02() 
{
	int a = 0x11223344;
	char* p = &a;
	printf("%x\n", *p);//44 低位字节数据 低地址
	printf("%x\n", *(p + 1));//33 高位字节数据 高地址
}
int main(void)
{
	test01();
	system("pause");
	return EXIT_SUCCESS;
}

空指针和野指针

空指针

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

野指针

1. 指针变量未初始化

2. 指针释放后未置空 
    free(p);
    后面没有跟 p=NULL;

3. 指针操作超越变量作用域
   在函数里指针指向的局部变量已经被销毁

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

示例

//1. 不允许想NULL和非法地址拷贝内存
void test()
{
	char* p = NULL;
	//给p指向的内存区域拷贝内容
	strcpy(p, '1111');//err

	char* q = 0x122;//直接给指针赋地址,非法地址
	//给q指向的内存区域拷贝内容
	strcpy(q, '2222');//err
}
//4)指针操作超越变量作用域
int* doWork() 
{
	int a = 10;
	int* p = &a;
	return p;
}
//2. 野指针出现情况
void test02() 
{
	//1)指针变量未初始化
	int* p;
	printf("%d\n", *p);
	//2)指针释放后未滞空
	char* str = malloc(100);
	free(str);
	//记住释放后置空,防止野指针出现
	str = NULL;
	free(str);
	//3)空指针可以重复释放,野指针不能重复释放;
	//4) 指针操作超越变量作用域
	int* p1 = doWork();
	printf("%d\n", *p1);
}
int main(void)
{

	system("pause");
	return EXIT_SUCCESS;
}

指针的步长

1. +1之后跳跃的字节数

2. 解引用 解出的字节数

3. 自定义结构体做步长练习

4. 通过 offsetof( 结构体名称, 属性)  找到属性对应的偏移量
    offsetof 引入头文件 #include<stddef.h>

示例

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

	double* p1 = NULL;
	printf("%d\n", p1);
	printf("%d\n", p1 + 1);
}

//2. 解引用的时候,解出的字节数量
void test02() 
{
	char buf[1024] = { 0 };
	int a = 1000;

	memcpy(buf+1, &a, sizeof(int));

	char* p = buf;
	printf("%d\n",*(int *)(p+1));
}

//步长练习,自定义数据类型练习
struct Person 
{
	char a;
	int b;
	char buf[64];
	int d;
};

void test03() 
{
	struct Person p = { 'a',10,"hello world",20 };
	printf("d属性的偏移量:%d\n", offsetof(struct Person, d));//需要引入stddef的头文件
	char* pp = &p;
	printf("%d\n", *(int*)(pp + 72));
}

int main(void)
{
	test03();
	system("pause");
	return EXIT_SUCCESS;
}

指针的间接赋值

需要满足三大条件

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

2. 建立关系

3. 通过*  操作内存

    利用Qt实现 操作地址  修改内存

image.png

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

输入特性

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

分配在栈上和堆区

输出特性

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

示例

//输入特性:主调函数分配内存,被调函数使用
void func(char* p) 
{
	strcpy(p, "hello world");
}

void test01() 
{
	//在test01中分配了内存,分配在栈上
	char buf[1024] = { 0 };

	func(buf);
	printf("%s\n", buf);
}

void printString(char* str) 
{
	printf("%s\n", str+6);
}

void test02() 
{
	char* p = malloc(sizeof(char) * 64);
	memset(p, 0, 64);
	strcpy(p, "helloworld");
	printString(p);

	if (p != NULL) 
	{
		free(p);
		p = NULL;
	}
}

//输出特性:在被调函数中分配内存,主调函数使用
void  allocateSpace(char **pp) 
{
	char* str = malloc(sizeof(char) * 64);
	memset(str, 0, 64);
	strcpy(str, "hello world");

	*pp = str;
}

void test03() 
{
	char* p = NULL;
	allocateSpace(&p);
	printf("%s\n", p);
}
int main(void)
{
	test03();
	system("pause");
	return EXIT_SUCCESS;
}

字符串强化训练

字符串结束标志 \0

sizeofstrlen统计的区别,sizeof统计整个数组长度。

拷贝字符串 利用三种方式

1. 利用[]

2. 利用指针

3.while (*dest++ = *src++){}

示例

void test01() 
{
	//字符数组只能初始个2个字符,当输出的时候,从开始位置直到找到\0结束
	char str1[] = { 'h','e' };
	printf("%s\n", str1);//err
	//字符数组部分初始化,剩余值填0
	char str2[100] = { 'g','g' };
	printf("%s\n", str2);//ok
	//如果以字符串初始化,那么编译器默认会在字符串尾部添加\0
	char str3[] = "hello";
	printf("%s\n", str3);//ok
	printf("%d\n", sizeof(str3));// 6个,包含\0 
	printf("%d\n", strlen(str3));//5 不包含\0

	char str4[100] = "hello";
	printf("%s\n", str4);//ok
	printf("%d\n", sizeof(str4));// 100
	printf("%d\n", strlen(str4));//5 不包含\0

	char str5[] = "hello\0world";
	printf("%s\n", str5);//只打印hello
	printf("%d\n", sizeof(str5));//12
	printf("%d\n", strlen(str5));//5个

	char str6[] = "hello\012world"; // \012看成一个转义字符 \0代表8进制,\012对应十进制\10对应ASCII码换行
	printf("%s\n", str6);//打印   hello换行world
	printf("%d\n", sizeof(str6));//12
	printf("%d\n", strlen(str6));//11个
}

//字符串拷贝实现
//1. 利用[]实现
void cpStr01(char * dest,char *src) 
{
	int len = strlen(src);
	for (size_t i = 0; i < len; i++)
	{
		dest[i] = src[i];
	}
	dest[len] = '\0';
}
//2. 利用字符串指针
void cpStr02(char* dest, char* src)
{
	while (*src !='\0')
	{
		*dest = *src;
		dest++;
		src++;
	}
	*dest = '\0';
}
//3. 利用while机制和指针偏移
void cpStr03(char* dest, char* src)
{
	while (*dest++ =*src++)
	{
	}
}
void test02() 
{
	char* str = "hello world";
	
	char buf[1024];

	cpStr03(buf, str);
	printf("%s\n", buf);

}
int main(void)
{
	test02();
	system("pause");
	return EXIT_SUCCESS;
}

翻转字符串

利用[ ]

利用指针

示例

//字符串翻转
//1. 利用[]
void revertStr01(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--;
	}
}
//2. 利用指针
void revertStr02(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--;
	}
}
void test03() 
{
	char str[] = "abcdefg";
	revertStr02(str);
	printf("%s\n", str);
}
int main(void)
{
	test03();
	system("pause");
	return EXIT_SUCCESS;
}

字符串的格式化:sprintf使用

格式化字符串

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

返回值 有效字符串长度

示例

void test01() 
{
	//1. 格式化字符串
	char buf[1024] = { 0 };

	sprintf(buf, "今天%d年 %d月 %d日\n", 2018, 6, 30);

	printf("%s", buf);
	//2. 拼接字符串
	memset(buf, 0, 1024);
	char str1[] = "hello";
	char str2[] = "world";

	int ret = sprintf(buf, "%s%s", str1, str2);//返回值是字符串长度,不包含\0
	printf("ret =%d\n", ret);

	//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);

}

int main(void)
{
	test01();
	system("pause");
	return EXIT_SUCCESS;
}