C++笔记day14

206 阅读8分钟

数据类型

数据类型是由编译器创建的,目的是为了更好的管理内存;
可以理解为创建变量的模具,固定大小内存的别名。

image.png

数据类型别名:typedef使用

起别名 -  简化struct关键字

区分数据类型

提高代码移植性

示例

//1. typedef使用 可以起别名,如简化struct 关键字
struct person 
{
	char name[64];
	int age;
};
typedef struct person myPerson;

void test()
{
	struct person p = { "aaa",16 };
	myPerson p2 = { "bbb",18 };
}
//2. 区分数据类型
void test1() 
{
	char* p1, p2;// p1是char *,p2是char;
	typedef char* PCHAR;
	PCHAR p3, p4;//p3 p4都是char *
	char* p5, * p6;//p5 p6都是char *
}
//3 提高移植性
typedef long long MYINT; //如果到更低版本跑不通这个类型了,只用改这一个地方就可
void test3() 
{
	MYINT a = 10;
	MYINT b = 10;
}

int main(void)
{

	system("pause");
	return EXIT_SUCCESS;
}

void使用

不可以利用void创建变量  无法给无类型变量分配内存

用途:限定函数返回值,函数参数

void * 万能指针  可以不通过强制类型转换就转成其他类型指针

示例

//1. void无类型,不可以通过void创建变量,原因无法给void无类型变量分配内存
void test01() 
{
	//void  a =10; 不可以
}
//2. 用途:限定函数返回值;限定函数的参数
void test02(void)//不写不报错,写了调用函数就不能有参数 
{
	//return 10; 会报错,void代表无返回值
}
//3. void * 万能指针,不管几级指针,任意类型指针都是4个字节,64位系统是8字节
void test03() 
{
	void* p = NULL;
	int* pint = NULL;
	char* pchar = NULL;

	pchar = (char*)pint;//需要强制类型转换
	pchar = p; //万能指针,可以不通过强制类型转换就转成其他类型指针

}

int main(void)
{

	system("pause");
	return EXIT_SUCCESS;
}

sizeof用法

本质:不是一个函数,是一个操作符

返回值类型 unsigned int无符号整型

用途:可以统计数组长度

示例

//1. sizeof本质 不是函数,而是操作符
void test01() 
{
	printf("size of int =%d\n", sizeof(int));
	double d = 3.14;
	printf("size of d = %d\n", sizeof d);//操作是变量的时候可以不加括号,函数则必须加括号;
}
// 2. sizeof 返回值类型 无符号整型
void test02() 
{
	unsigned int a = 10;
	//当unsigned int 和 int做运算,会统一转换成unisnged int 数据类型 
	if (sizeof(int)-10 > 0)
	{
		printf("大于0\n");
	}
	else 
	{
		printf("小于0\n");
	}
	if (a - 20 > 0)
	{
		printf("大于0\n");
	}
	else
	{
		printf("小于0\n");
	}
}
//3. sizeof 用途,统计数组长度
void calArr(int arr[]) 
//当数组名做函数参数的时候,会退化为指针,指向数组中第一个元素的位置
{
	printf("calArr length =%d\n", sizeof(arr));
}
void test03() 
{
	int arr[] = { 1,2,3,4,5,6,7,8 };
	printf("arr length =%d\n", sizeof(arr));
	calArr(arr);
}
int main(void)
{
	test02();
	system("pause");
	return EXIT_SUCCESS;
}

变量的修改方式

直接修改

直接赋值

间接修改

通过指针修改

示例:对自定义数据类型做练习

//变量的修改方式
void test01() 
{
	//1. 直接修改
	int a = 10;
	a = 20;
	//2. 间接修改
	int* p = &a;
	*p = 30;
	printf("a =%d\n", a);
}

struct Person 
{
	char a;
	int b;
	char c;
	int d;
};

void test02() 
{
	struct Person p = { 'a',10,'b',20 };
	// 直接修改 d 属性
	p.d = 1000;
	//间接修改 属性
	struct Person* pp = &p;
	pp->d = 100;

	printf("%d\n", pp);
	printf("%d\n", pp + 1);//加1移动了整个struct的长度;
	//如果想通过这种方法对struct内的属性进行修改,可以如此:
	char* pc = &p;
	*(int*)(pc + 12) =2000;//强转成int *再用*解引用
	printf("p的d属性为:%d\n", p.d);
	printf("p的d属性为:%d\n", *(int*)(pc + 12));
	printf("p的d属性为:%d\n", *(int*)((int*)pc + 3));
}

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

内存分区

运行前

程序编译过程:
1)预处理:宏定义展开、头文件展开、条件编译,不检查语法
2)编译:检查语法,将预处理后的文件编译生成汇编文件
3)汇编:将汇编文件生成目标文件(二进制文件)
4)链接:将目标文件链接为可执行程序

image.png

代码区text:

放代码; 共享  只读

数据区

存放数据:全局变量、静态变量、常量

已初始化数据区:data

存放数据:已赋初值的全局变量 、静态变量、常量

未初始化数据区:bss

存放数据:未赋初值的全局变量、静态变量、常量
在程序执行前被内核初始化为0NULL(空)

运行后

栈stack:

栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。
在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间。
编译器自动管理分配和释放,有限容量

堆heap:

是一个大容器,容量远远大于栈,不是无限。
没有栈那样先进后出的顺序,用于动态内存分配,堆在内存中位于BSS区和栈区之间。
手动开辟 malloc 手动释放 free。
若不释放,程序执行完后由系统回收。

栈区

符合先进后出数据结构

注意事项:不要返回局部变量的地址,局部变量在函数执行之后就被释放了,释放的内存就没有权限取操作了,如果操作结果未知。

image.png

示例

int myFunc() 
{
	int a = 10;
	return &a;
}
void test01() 
{
	//局部变量a早已被释放,因此我们没有权限操作这块内存空间
	int* p = myFunc();
	printf("*p =%d\n", *p);
	printf("*p=%d\n", *p);
}
char* getString() 
{
	char str[] = "hello world";
	return str;
 }
void test02() 
{
	//局部变量地址已经被释放,没有权限操作
	char* p = NULL;
	p = getString();
	printf("%s\n", p);
}

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

堆区

利用malloc在堆区创建数据

利用free释放堆区

注意事项:主调函数没有分配内存,被调函数需要用更高级的指针去修饰低级指针,进行分配内存

同级指针修饰内存失败解析图

image.png

高级指针给修饰低级指针成功解析图

image.png

示例

int* getSpace() 
{
	int* p = malloc(sizeof(int) * 5);
	if(p==NULL)
	{
		return;
	}
	for (int i = 0; i < 5; i++) 
	{
		p[i] = i + 100;
	}
	return p;
}
void test01() 
{
	//堆区内存不会自动释放
	int* p = getSpace();
	for (size_t i = 0; i < 5; i++)
	{
		printf("%d\n",p[i]);
	}
	//手动开辟,手动释放
	free(p);
	p = NULL;
}
//注意事项:下面被调函数使用低级指针分配内存不可行;
void allocateSpace(char* pp) 
{
	char* temp = malloc(100);
	memset(temp, 0, 100);
	strcpy(temp, "helloworld");
	pp = temp;
}

void test02() 
{
	char* p = NULL;
	allocateSpace(p);
	printf("%s\n", p);
}
//注意事项:下面被调函数使用高级指针分配内存可行;
void allocateSpace1(char** ppc)
{
	char* temp = malloc(100);
	memset(temp, 0, 100);
	strcpy(temp, "helloworld");
	* ppc = temp;
}

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

static 和extern 区别

static:特点:在运行前分配内存,程序运行结束 生命周期结束  ,在本文件内都可以使用静态变量
静态变量只能调用一次。
extern:可以提高变量作用域,在c语言下,全局变量前都隐式的加了extern关键字

示例

//static 静态变量
//特点:在运行前分配内存,程序运行结束,生命周期结束,在本文件内都可以使用
//全局作用域a
static int a = 10;

void test01() 
{
	//局部作用域b
	static int b = 20;
}

int main(void)
{
	//告诉编译器 下面代码中出现g_a不要报错,是外部链接属性,在其他文件中
	extrern int p;
	//如果外部文件中找不到g_a,会报错1个无法解析的外部命令,是链接阶段的错误
	system("pause");
	return EXIT_SUCCESS;
}

常量

const修饰的变量

全局变量

直接修改 失败 ,间接修改 语法通过,运行失败,受到常量区保护

局部变量

直接修改 失败 , 间接修改  成功,放在栈上

字符串常量

vs 将多个相同字符串常量看成一个;
不可以修改字符串常量
ANSI并没有制定出字符串是否可以修改的标准,根据编译器不同,可能最终结果也是不同的

示例

//1. const修饰的变量
//1) 全局变量
const int a = 10;//常量区,间接修改,语法通过,运行失败,原因:受到常量区保护
void test01() 
{
	//a=100;直接修改修改不了
	int* p = &a;
	*p = 100;
	printf("%d\n", a);
}
//2) 局部变量
void test02() 
{
	const int b = 10;
	//b=20;直接修改失败;
	int* p = &b;
	*p = 20;
	printf("%d\n", b);
	//在C语言中,const修饰的局部变量,成为伪常量,不可以初始化数组
	//int arr[b];
}
//2. 字符串常量
void test03()
{
	char* p1 = "hello world";
	char* p2 = "hello world";
	char* p3 = "hello world"; //同一份字符串常量共用一个地址
	printf("%d\n", &"hello world");//字符串常量可以直接取地址;
	printf("%d\n", p1);
	printf("%d\n", p2);
	printf("%d\n", p3);
}
void test04() 
{
	char* str = "hello world";
	str[0] = 'x';//修改字符串常量结果未定义,visual studio里不允许
}
int main(void)
{
	test04();
	system("pause");
	return EXIT_SUCCESS;
}