数据类型
1.1数据类型基本概念
类型是对数据的抽象;
类型相同的数据具有相同的表示形式、存储格式以及相关操作;
程序中所有的数据都必定属于某种数据类型;
数据类型可以理解为创建变量的模具: 固定大小内存的别名;
数据类型的重要性,还体现在它可以告诉编译器分配多少内存可以放得下我们的数据
注1:运行以下代码,当使用如下所示创建变量的时候,char* 会修饰的是距离最近的那个变量
另外一个变量依旧为char类型。
void test02()
{
char* p1 = 0, p2 = 1;
printf("%d\n", sizeof(p1));
printf("%d\n", sizeof(p2));
}
注2:使用以下方式定义结构体,同样会报错,是因为该结构体的定义运用到了类似于递归的概念
在编译器预先分配内存的时候,由于不知道应该分配的大小,因此会产生错误。
typedef struct Person
{
char* name;
int age;
struct Person p;
}myPerson;
注3:当使用数据类型为unsigned int 类型的数据进行运算的时候,结果一般也为unsigned int
类型,因此,使用此作为条件判断的时候,可能会出现意外的错误。
void test02()
{
unsigned int a = 10;
if (a - 20 > 0)
printf("大于0");
else
printf("小于0");
}
1.2typedef的用法
使用typedef的作用,主要在注重于一致性,假如面对代码量较大的文件或者是在不同的编
译器上运行相同的代码,可能会需要修改数据类型,使用typedef函数可以节省很大一部分时间
同时也可以减少我们的代码量。
1.3 void数据类型
void字面意思是”无类型”,也就是万能指针,可以接收任意类型的指针变量。
void真正用在以下两个方面:
对函数返回的限定;
对函数参数的限定;
1.4 sizeof操作符
基本语法:
sizeof(变量);
sizeof 变量;
sizeof(类型);
sizeof 注意点:
1.sizeof返回的占用空间大小是为这个变量开辟的大小,而不只是它用到的空间。对结
构体使用的时候,大多情况下得考虑字节对齐(结构体所占字节的大小一般为其中最大数
据类型的整数倍)的问题;
2.sizeof返回的数据结果类型是unsigned int,
3.要注意数组名和指针变量的区别。通常情况下,我们总觉得数组名和指针变量差不多,
但是在用sizeof的时候差别很大,对数组名用sizeof返回的是整个数组的大小,而对指针变量
进行操作的时候返回的则是指针变量本身所占得空间,在32位机的条件下一般都是4。而且
当数组名作为函数参数时,在函数内部,形参也就是个指针,所以不再返回数组的大小;
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int countArrsize(int arr[])
{
return sizeof(arr);
}
void test01()
{
int arr[] = { 1,2,3,4,5,6,7 };
printf("sizeof arr:%d\n", sizeof(arr));
printf("sizeof arr:%d\n", countArrsize(arr));
}
int main()
{
test01();
return 0;
}
内存分区
1.程序运行之前
程序没有加载到内存前,可执行程序内部已经分好3段信息,分别为代码区(text)、
数据区(data)和未初始化数据区(bss)3 个部分(有些人直接把data和bss合起来叫做静
态区或全局区)。
代码区
存放 CPU 执行的机器指令。通常代码区是可共享的(即另外的执行程序可以调用它),
使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通
常是只读的,使其只读的原因是防止程序意外地修改了它的指令。另外,代码区还规划了
局部变量的相关信息。
全局初始化数据区/静态数据区(data段)
该区包含了在程序中明确被初始化的全局变量、已经初始化的静态变量(包括全局静态变
量和局部静态变量)和常量数据(如字符串常量)。
未初始化数据区(又叫 bss 区)
存入的是全局未初始化变量和未初始化静态变量。未初始化数据区的数据在程序开始
执行之前被内核初始化为 0 或者空(NULL)。
总体来讲说,程序源代码被编译之后主要分成两种段:程序指令和程序数据。代码段属于
程序指令,而数据域段和.bss段属于程序数据。
2. 运行之后
程序在加载到内存前,代码区和全局区(data和bss)的大小就是固定的,程序运行期间不
能改变。然后,运行可执行程序,操作系统把物理硬盘程序load(加载)到内存,除了根据可
执行程序的信息分出代码区(text)、数据区(data)和未初始化数据区(bss)之外,还额
外增加了栈区、堆区。
栈区(stack)
栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、
局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释
放该段栈空间。
堆区(heap)
堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序。用于动态内存
分配。堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序
结束时由操作系统回收。
3.栈区
由系统进行内存的管理。主要存放函数的参数以及局部变量。在函数完成执行,系统自行
释放栈区内存,不需要用户管理。
4.堆区
由编程人员手动申请,手动释放,若不手动释放,程序结束后由系统回收,生命周期是整
个程序运行期间。使用malloc或者new进行堆的申请。
对于连续的内存空间,都可以使用下标[]的方式来访问其中的元素。
注:如以下代码所示操作,在子函数中开辟堆空间,会导致内存泄漏的问题。
下面的第一个函数的写法,是错误的,容易造成内存泄漏,因为如下面这样的操作,本质其
实是对于调用的函数中拷贝的形参变量的操作,与传递进来的实参变量无关,在函数执行结束
后,程序释放,相应的调用的子函数中的各种变量包括形参变量也就跟着一起释放掉了,就会
导致开辟的那块堆空间内存泄漏。
正确写法是第二个子函数。
(之前C入门大作业中的文件内容翻译中,就遇到了在子函数中开辟空间导致内存泄漏,具体写法见前面)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void allocateSpace(char* p)
{
p = malloc(100);
memset(p, 0, 100);
strcpy(p, "hello world");
}
void test02()
{
char* p = NULL;
allocateSpace(p);
printf("p = %s\n", p);
}
void allocateSpace02(char** p)
{
*p = malloc(100);
memset(*p, 0, 100);
strcpy(*p, "hello world");
}
void test03()
{
char* p = NULL;
allocateSpace02(&p);
printf("p = %s\n", p);
}
int main()
{
test03();
return 0;
}
5.全局/静态区
全局静态区内的变量在编译阶段已经分配好内存空间并初始化。这块内存在程序运行期间
一直存在,它主要存储全局变量、静态变量和常量。
注:
(1)这里不区分初始化和未初始化的数据区,是因为静态存储区内的变量若不显示初始化,
则编译器会自动以默认的方式进行初始化(初始化为0),即静态存储区内不存在未初始化的
变量。
(2)全局静态存储区内的常量分为常变量和字符串常量,一经初始化,不可修改。静态存储
内的常变量是全局变量。
与局部常变量不同,区别在于局部常变量存放于栈,实际可间接通过指针或者引用进行修
改,而全局常变量存放于静态常量区则不可以间接修改。
(3)字符串常量存储在全局/静态存储区的常量区。
5.1 全局变量
与全局变量对比,静态全局变量是不同的,全局变量默认外部链接,静态全局变量默认内部
链接,二者的区别在于:
1.如果变量是内部链接的话,那么此变量的作用域为当前文件
2.如果变量是外部链接的话,那么此变量的作用域为当前项目
5.2 常量区
1.const常量
const全局变量和局部变量的区别
const全局变量在常量区,直接或间接都不能修改
const局部变量在栈区,直接或不能修改,间接可以修改(不存在于常量区,因为常量区在堆区中,局部const存储于栈区)
2.字符串常量