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