typedef
1、给已知类型取别名
struct Person {
char name[64];
int age;
};
typedef struct Person myPerson;
等价于
typedef struct Person{
char name[64];
int age;
}myPerson;
2、定义两个指针类型
#include<iostream>
using namespace std;
typedef char* PCHAR;
int main() {
PCHAR p1, p2;
cout << typeid(p1).name() << endl;
cout << typeid(p2).name() << endl;
return 0;
}
运行结果:
3、有利于程序的移植性
如果你程序里用的long long,在另一个环境里没有long long,此时你需要把这些long long都改成int,会很繁杂。如果你typedef logn long为一个代名词,只需要改一下typedef就可以了。
void的用法
void不能直接定义变量,因为编译器不知道分配多少内存。
下面的情况,编译器也不知道分配多少内存
struct Person {
int age;
struct Person p;
};
1、对函数返回的限定
指针不管什么类型的,都是4字节。
下面的情况,编译器也不知道分配多少内存
void help() {
}
2、对函数参数的限定
int help(void) {
}
3、void* 无类型指针
任何类型的指针都可以不经过强制转换,转换成void*类型的指针。
void*主要要用于数据结构的封装。
sizeof操作
1、sizeof可以告诉我们编译器为某一特定数据或者某一类型的数据在内存中分配空间时分配的大小,大小以字节为单位。
2、sizeof返回的占用空间大小是为这个变量开辟的空间,而不只是他用到的空间。
3、sizeof返回的数据结果类型是unsigned int(也就是没有负数的)
sizeof与字节对齐
下面这段代码的sizeof结果是——5字节
#pragma pack(1) //设置对齐模式
struct Person {
char a;
int b;
};
下面这段代码的的sizeof结果是——8字节
struct Person {
char a;
int b;
};
sizeof与unsigned int
大部分编译器对于无符号类型和有符号类型数字之间进行计算之后,产生的结果就是无符号类型的数。例如下面的代码:
#include<iostream>
using namespace std;
void test() {
unsigned int a = 10;
if (a - 20 > 0) {
cout << "大于0" << endl;
}
else {
cout << "小于0" << endl;
}
}
int main() {
test();
return 0;
}
运行结果:
sizeof与数组参数传递
数字作为函数参数会退化为指向数组首元素的指针,例如下面的代码:
#include<iostream>
using namespace std;
int calculate(int arr[]) {
return sizeof(arr);
}
void test() {
int arr[] = { 1,2,3,4,5,6,7 };
cout << "数组arr的大小:" << sizeof(arr) << endl;
cout << "作为函数参数的数组大小为:" << calculate(arr) << endl;
}
int main() {
test();
return 0;
}
运行结果:
变量的间接赋值
变量代表的就是一块内存
#include<iostream>
using namespace std;
void test() {
//直接赋值
int a = 20;
a = 100;
//间接赋值
int *p = &a;
*p = 200;
}
struct Person {
char a;
int b;
char c;
int d;
};
void test2() {
struct Person p = { 'a',100,'b',200 };
cout << p.d << endl;
p.d = 1000;
cout << p.d << endl;
cout << "-----------------------------------------" << endl;
int *pp = nullptr;
cout << "int类型的pp的地址为:" << pp << endl;
cout << "int类型的pp+1的地址为:" << pp + 1 << endl;
cout << "------------------------------------------" << endl;
double *ppp = nullptr;
cout << "double类型的pp的地址为:" << ppp << endl;
cout << "double类型的pp+1的地址为:" << ppp + 1 << endl;
//因此对于结构体,如果用 &p+1的话,就直接跳到结构体的最尾部了
//我们希望可以通过指针来修改结构体内部的d的值
//因此我们将其调整为char*类型,这样我们就可以保证他每次加1的时候跳1个字节
cout << "结构体找到的d的地址为:" << &(p.d) << endl;
cout << "指针找到的d的地址为:" << (char *)&p+12 << endl;
//获取地址d的值 (int *)告诉编译器获取的地址存储的值占多少字节
cout << "d的值为:" << *(int *)((char *)&p + 12) << endl;
}
int main() {
test2();
return 0;
}
运行结果:
内存分区
执行c程序,需要经历的步骤
1、预编译:宏定义展开、头文件展开、条件编译、不会检查语法
2、编译:检查语法、将预处理之后的文件编译生成汇编文件
3、汇编:将汇编文件生成目标文件(二进制文件)
4、链接:将目标文件链接为可执行程序
代码区
存放CPU执行的机器指令、只读、共享(为了节省资源)
全局初始化数据区、静态数据区(data段)
1、存放已经初始化的全局变量、已经初始化的静态变量、常量数据(如字符串常量)
2、程序在被加载到内存之前,代码区和全局区的大小就是固定的。
3、堆去存放程序员自己开辟自己管理的数据
4、编译时期分配内存只是分配地址,运行时期分配的内存,才是真的内存
//其实是:(extern) int a; 全局变量a 外部链接属性
//比如你在a.c里面定义的,那么你在b.c里面是可以用它的
int a;
//内部链接属性 只在当前文件中可见
static int b;
什么数据会被放在常量区?
1、字符串常量、
2、const修饰的全局变量
常量区中的数据一旦初始化就不能再修改,只读的内存。
栈区
自动申请、自动释放
函数参数,函数变量,函数返回值都是在栈上。
不要去试图free一个栈上的内存,会报错。
观察下面程序的运行结果
#include<iostream>
using namespace std;
int * test(){
int a = 10;
return &a;
}
char * test2() {
char arr[] = "hello, word!";
return arr;
}
void test1() {
//此时a的内存已经被释放
cout << *(test()) << endl;
char *s = nullptr;
s = test2();
cout << s << endl;
}
int main() {
test1();
return 0;
}
运行结果:
为什么test2的结果不是“hello word!”
因为s存储的是test2()返回的一个指向栈内存的指针str(常量区是内容“hello word”拷贝一份到栈内存中,并使str指向该内存),而内存中的变量随着test2的运行结束而被销毁,因此地址0x002中的内容是随机的
堆区
++j 比 j++ 的效率高,因为 j++要保存当前的状态,会多一个临时变量
int j=0;
int a=j++;
则此时a的值为0
1、对的内存成员手动申请,手动释放
#include<iostream>
using namespace std;
int *get() {
int *p = (int*)malloc(sizeof(int) * 5);
if (nullptr == p) {
return nullptr;
}
//只要是连续的内存空间,都能使用下标的方式访问内存
for (int i = 0; i < 5; ++i) {
p[i] = 100 + i;
}
return p;
}
void test1() {
int *ret = get();
for (int i = 0; i < 5; ++i) {
cout << ret[i] << endl;
}
free(ret);
ret = NULL;
}
int main() {
test1();
return 0;
}
2、定义变量时,一定要初始化,因为很多bug的产生,都是因为没有初始化产生的
下面代码的返回值为nullptr
#include<iostream>
using namespace std;
void alloc(char* p) {
p = (char *)malloc(100);
memset(p, 0, 100);
strcpy(p, "hello word");
}
void test1() {
char *p = nullptr;
alloc(p);
cout << "p=" << p << endl;
}
int main() {
test1();
return 0;
}
为什么?
栈中存放了一个test1()里面的p指针,指针指向的内容为nullptr。谈话调用malloc函数,传入指针p,此时函数malloc的栈上有了一块指针p,这个指针指向了0x888,而0x888存储了从常量区拷贝到堆内存的“hello word”。
函数调用完毕,却没有返回值,所以cout的结果是nullptr。
改成这样就可以了
#include<iostream>
using namespace std;
void alloc(char** p) {
char *temp = (char *)malloc(100);
memset(temp, 0, 100);
strcpy(temp, "hello word");
*p = temp;
}
void test1() {
char *p = nullptr;
alloc(&p);
cout << "p=" << p << endl;
}
int main() {
test1();
return 0;
}
为什么?
全局静态区
//extern int a=10; 默认是外部链接
int a = 10;//全局区
//静态全局变量是内部链接
static int b = 20;//静态区
void test1() {
static int c = 30;//静态区
}
上面代码中,c只在函数内可见,b只在文件内可见。
1、全局静态变量和局部静态变量都存储在静态区,都是在程序运行期间都是合法有效
2、局部静态变量符号的可见范围仅限于当前函数内部,全局静态变量可见范围从定义到文件结尾
内部链接和外部链接有什么区别(extern和static的区别)
1、如果变量是内部链接,那么变量只能在当前文件诶访问
2、如果变量是外部链接,那么此变量可以被外部文件使用
编译的时候只编译 .c 文件, .h 文件是不编译的,我们把一个 .c 文件叫做一个编译单元,编译器独立编译每一个 .c 文件。
外部链接extern的使用的例子
void test(){
//声明,表示告诉编译器这个符号是存在的,你让我先编译通过,让连接器去找这个符号在哪
//连接器会去别的文件里找这个g_a
extern int g_a;
cout<<g_a<<endl;
}
如果连接器没有找到g_a,则会报错:
头文件里面不放定义,只放声明。
常量区
放置字符串常量和全局const常量
const全局const变量和局部变量的区别
堆栈上的内存都可以修改,只是能不能直接修改的问题。
1、const全局变量在常量区,一旦初始化,直接或者间接都不可能将其修改
(比如下面的代码就会发生写入时报错)
2、const局部变量,可以通过指针进行间接修改
字符串常量
#include<stdio.h>
void test1() {
const char *p = "hello word!";
printf("%d\n",&"hello word!");
printf("%d\n",p);
}
int main() {
test1();
return 0;
}
运行结果
ANSI C 并没有规定字符串常量是否可以修改,因此有的编译器支持修改字符串常量,有的不支持。
支持字符串常量修改的编译器把每个字符串常量都存在了不同的地址中
不支持修改字符串常量的编译器,如果两个指针指向相同经的字符串常量,则题目指向的是相同的地址。
总结
数据区:堆栈、全局/静态存储区
全局/静态存储区:常量区、全局区、静态区
常量区:字符串常量区、常变量区(也就是全局const变量)
代码区:存在程序编译后的二进制代码,不仅可寻址区。