c语言学习笔记8

一、计算机组成原理(内存)

1.内存编址

(1)内存在逻辑上就是一个一个的格子,这些格子可以用来装东西,也就是内存中的数据,每个格子都有一个固定的编号,这个编号0、1、2、3就是内存地址,这个内存地址(一个数字)和这个格子的空间是一一对应的并且是永久绑定的。

c语言学习笔记8_第1张图片
(2)cpu在内存或硬盘里面寻找一个数据时,先通过地址线找到地址,然后再通过数据线将数据取出来。

(3)在程序运行时,CPU只认识内存地址,而不关心这个地址所代表的空间在哪里以及分布,因为硬件的设计保证了只要有地址,就一定能找到这个盒子在哪里,所以内存单元有两个概念:地址和空间。

(4)内存编址是以字节为单位,每一个内存地址对应的内存大小的空间是固定的,就是一个字节。

2.内存寻址

  1. 逻辑地址:包含在机器语言指令中用来指定一个操作数或者一条指令的地址
  2. 线性地址(也称虚拟地址):是一个32位无符号整数,可以用来表示高达4G的地址,通常使用16进制数来表示,范围从0x00000000到0xffffffff
  3. 物理地址:用于内存芯片级内存单元寻址,他们从微处理器的地址引脚发送到内存总线上的电信号相对应。物理地址由32位或36位无符号整数表示

3.不同位数的系统的寻址空间

32位/64位系统----32根/64根地址线----物理线用通电来记录----用1/0来编写地址线

  • 首先从二进制说起,每一bit都有2种取值的情况,也就是说可以表示2个存储区间
  • 那么n位2进制数可以表示的存储区间数位2^n种
  • 每个存储单元是8bit = 1B
  • 所以n位地址总线可以表示的存储区间位:2^n Byte

32位系统支持ram最大为2^32type即4GB

64位系统支持ram最大为2^64type即128GB

4.栈区内存的使用

先用高地址空间,再用低地址空间。

数组与变量之间的内存占据情况:

vs 6.0h环境下——空0个整型

gcc环境下——空一个整型

VS2013——VS2019——空1或2个整型

二、指针

注:相较于函数中的传值调用,传址调用更为优势,可以减少创建局部变量所开辟内存的使用,从而减少内存的占用。

数据在内存中的情况

c语言学习笔记8_第2张图片

c语言学习笔记8_第3张图片

  •  采用十六进制来表示地址和其数值
  • 一位表示一字节
  • 使用第一个字节的地址表示变量的地址
  • 数值表示从首位往后排列
  • 执行同一个程序每次输出的变量地址不一样(地址空间随机化分布,相当于重新开辟一个空间)

#include
void main()
{
	int a = 13;
	printf("%p\n", &a);
}

“&p”,&变量专门用来打印地址 

c语言学习笔记8_第4张图片

c语言学习笔记8_第5张图片

指针变量创建

创建并格式化指针变量:数据类型* +名称 = &+对应变量的名称

例如:

int a = 10;
char ch = 'h';

int* p1 = &a;
//创建指针变量p1来储存a的地址
char* p2 = &ch;
//创建指针变量p2来储存ch的地址

指针的使用

#include
void main()
{
	int a = 13;
	int* pa = &a;
	*pa = 20;
//* 解引用操作
//*pa通过pa里面的地址来找a
	printf("%d %d", a,*pa);
}

测量指针变量的大小

32位下指针变量为4字节

64位下指针变量为8字节

指针大小与地址线数量有关,而地址线与系统位数相关

数组中的指针

int arr[5] = {0};
arr = &arr[0];
//arr数组名称的指针储存的是数组的首元素的地址
&arr
//&arr取出的是整个数组的地址

c语言学习笔记8_第6张图片

&arr与&arr+1相差20字节

arr与arr+1相差4字节

注:在函数中传入的数组为数组首位的指针,故在函数中利用sizeof计算的结果为指针变量的内存大小。

指针的类型

理解:指针的类型决定指针向前或后走一步的距离

void main()
{
	int arr[] = { 0 };
	int* p = arr;
	char* cp = arr;
	printf("%p\n", p);
	printf("%p\n", p + 1);
	printf("%p\n", cp);
	printf("%p\n", cp + 1);
}
//输出结果第一行地址与第二行地址相差4
//输出结果第三行地址与第四行地址相差1

example:

void main()
{
	int arr[] = {1,2,3,4,5};
	short* p = (short*) arr;
	for (int i = 0; i < 4; ++i)
	{
		*(p + i) = 0;
	}
//由于p是short类型的指针变量,所以移动单位为2字节
	for (int i = 0; i < 5; ++i)
	{
		printf("%d ", *(arr + i));
	}
}
//输出结果为0 0 3 4 5

万能指针(void*)

void*

可以接受任何类型的指针

 (3条消息) 类qsort的多用冒泡排序函数_HS-Cai的博客-CSDN博客https://blog.csdn.net/qq947467490/article/details/128068613

避免野指针

1.对指针初始化

2.避免指针越界

3.指针指向空间释放即使置NULL

int* p = NULL;
//当不知道p该初始化什么地址的时候,直接初始化为NULL

二级指针

创建与初始化

c语言学习笔记8_第7张图片

指针数组传参使用二级指针

void test2(int** arr2)
{}
void main()
{
	int* arr2[10] = { 0 };
	test2(arr2);
}

指针数组的名称为首元素的指针的地址

三、结构体

单独结构标记的声明

#include
//单独声明结构标记
struct BOOK
{
	char name[20];
//创建成员变量
	int page;
	float price;
};
void main()
{
	struct BOOK s = { "Rich Dad Poor Dad",351,48.00 };
//创建并初始化结构体变量s
	printf("%s %d %.2f$", s.name, s.page, s.price);
//对结构体变量s中的成员变量进行打印
}

如果加上指针的运用可以写成

#include
struct BOOK
{
	char name[20];
	int page;
	float price;
};
void main()
{
	struct BOOK s = { "Rich Dad Poor Dad",351,48.00 };
	struct BOOK* p = &s;
//此处应注意数据类型,来创建指针变量
	printf("%s %d %.2f$", p->name, p->page, p->price);
}

如果先创建结构体变量后格式化可以写成

#include
struct BOOK
{
	char* name;
	int page;
	float price;
};
void main()
{
    struct BOOK s1;
	s1.name = "RICH";
	s1.page = 111;
	s1.price = 15.80;
	printf("%s\n%d\n%.2f$", s1.name, s1.page, s1.price);
}

[重点]注:如此定义结构体必须要使用指针来创建字符串

结构标记的声明与结构变量的定义一起

#include
struct BOOK
{
    char name[20];
    int page;
    float price;
}s1 = { "Rich Dad Poor Dad",351,48.00 }, s2;
//同时声明了结构体变量s1,s2
//此时s1,s2为全局变量
void main()
{
    printf("%s\n%d\n%.2f$", s1.name, s1.page, s1.price);
}

结构类型的定义

#include
//利用typefed函数将struct BOOK重定义为book
typedef struct BOOK
{
	char name[20];
	int page;
	float price;
}book;
void main()
{
	book s1 = { "Rich Dad Poor Dad",351,48.00 };
	printf("%s\n%d\n%.2f$", s1.name, s1.page, s1.price);
}

*结构体嵌套使用

//嵌套结构体的使用
#include
struct publication
{
	int year;
	int month;
	int day;
};
struct BOOK
{
	struct publication c;
	char name[20];
	int page;
	float price;
};
void main()
{
	struct BOOK s1 = { {1997,11,14} , "Rich Dad Poor Dad" , 351 , 48.00 };
	printf("%d.%d.%d\n%s\n%d\n%.2f$", s1.c.year, s1.c.month, s1.c.day, s1.name, s1.page, s1.price);
}

注:嵌套使用另一个结构体需要,在主结构体中创建所嵌套使用的结构体变量。 

*结构体数组

#include
struct book
{
	char* name;
	int page;
	float price;
};
void print(struct book* p)
{
	printf("%s", p->name);
}
void main()
{
	struct book s[3] = { {"POOR", 168, 28.00} ,{"RICH", 172, 25.00} ,{"WINNER", 180, 24.00} };
	print(s + 1);
}

结构体的传参选择

函数传参的时候,参数是需要压栈的。如果传递—个结构体对象的时候,结构体过大,参数压栈的时候系统开销会比较大,所以会导致性能的下降。
结论:结构体传参的时候,要传结构体的地址。

example:

将结构体变量b的地址传给函数print

#include
struct publication
{
	int year;
	int month;
	int day;
};
struct BOOK
{
	struct publication s2;
	char* id;
	int page;
	float price;
};
void print(struct BOOK* b)
{
	printf("%d.%d.%d\n%s\n%d\n%.2f\n", b->s2.year, b->s2.month, b->s2.day, b->id, b->page, b->price);
}
void main()
{
	struct BOOK b = { {1997,11,14},"POOR OR RICH",197,25.00 };
	print(&b);
}

*结构体大小计算

1、找到结构体中最大的成员变量所占的字节数

2、结构体中成员变量的起始地址为该成员变量大小的整数倍(一般情况下我们认为第一个成员变量的起始地址为0)

3、结构体变量的总大小必须要是结构体中最大变量字节的整数倍(不足补齐)

example:

struct demo{
    short a;
    double b;
    int c;
}demo;

c语言学习笔记8_第8张图片
a从0开始占2个字节,因为b为8个字节所以起始地址必须从8开始到16,c变量起始地址从16开始到20,总共20个字节,但是因为结构体变量的总大小必须为8字节的倍数,所以补齐为24

你可能感兴趣的:(c语言学习笔记,c语言)