内存
C语言内存四区:代码区,全局区,栈区,堆区
代码区:存放代码的
全局区:用于存放全局变量和静态变量, 里面细分有一个常量区,字符串常量和其他常量也存放在此。该区域是在程序结束后由操作系统释放。
栈区:由系统自动开辟,系统自动释放,并不大。
堆区:程序员动态开辟的内存,由我们手动开辟,手动释放,非常大。链表,数据结构,动态数组,动态结构体与此相关。
地址
一个字节8位,内存以单个字节位单位分开,每个字节有一个不可修改的连续的唯一的编号,这个编号就是地址。就像酒店里的房间。一个一个的房间就是一个一个字节,编号,也就是地址,就是门牌号,而且是刻在房间门上的,不可修改。
每个在代码里的变量都有地址,用取地址运算符:& (单目运算符,优先级仅低于“()”,从右往左)来获取地址。
首地址
一段内存空间中,第一个存储单元的地址。如定于一个int a,占用内存中4个字节,首地址就是这四个字节中,第一个字节的地址。如图:
第二个数组的首地址是他的第一个存储单元,也就是a[0]的地址,实际上a[0]的首地址就是他的第一个字节的首地址,数值上看是一样的,但是意义并不同
指针变量
地址如上所说,是一种编号,是一种数据。用来存放地址的变量,
内存大小位4B(4个字节)
整数 int a;
字符 char b;
小数 float c;
地址 指针变量
指针变量的定义:指针变量不是单独存在的,是相对与其他数据类型来的,比如整形指针变量 int *p;,字符型指针变量 char *p; 。
int *p;//指明一个指针变量,存的数据为地址。int表明存放的地址指向的内存空间里面存放的数据类型是int。*表明p这个变量是指针变量。这个指针变量名字就叫做p(不是*p)。其他的类型(char,float,……)相同
指针变量的赋值:
int a; int *p; a=5; p=&a;
加入a的地址是1001,此时p中存的是a的地址,即1001,称p指向a
指针变量的引用:接上一条的代码,访问a的值。可以直接用a来访问,如一条printf语句,当然这里说指针,看到要用指针来访问,就像第二条printf语句。
#include <stdio.h> #include <stdlib.h> int main() { int i; int *pi; i = 5; pi = &i; printf("i的值为:%d ", i); printf("i的值为(指针):%d ", *pi); return 0; }
指针访问:*指针变量(*为取值运算符,单目运算符,从右到左)(在定义指针变量时,*只是表明后面接的变量是一个指针变量。在未定义时出现的*,表示为取值运算符。
野指针:不能明确指向的指针变量 ,当一个指针变量指向一个为定义的变量时,这个指针变量就是野指针。(很危险!!!!有可能在未知情况下操作意料之外的数据)
解决部分,定义该指针为NULL,即内存中的第一个字节的地址,0
空指针:空指针相对于int*,float*,char*,等,空指针为void*,不知道指向什么样子的内存,比如一个4个字节的内存,可以存int,long等类型的数据,未确定时,使用void,当确定下来后,可以强制把void转化需要的类型。在后期动态内存分配,用到malloc等函数时用得更多
指针的运算
只有4个,+, -, ++, --
单纯指针加减没有意义,主要是为了指针偏移。指针变量的加减,以指针所指向的类型空间为单位进行偏移。如
#include <stdio.h> #include <stdlib.h> int main() { int i; char c; int *pi; char *pc; i = 5; c = 'c'; pi = &i; pc = &c; printf("i的值为:%d ", i); printf("pi的值为:%p ", pi); printf("pi+1的值为:%p ", ++pi); printf(" "); printf("c的值为:%c ", c); printf("pc的值为:%p ", pc); printf("pc+1的值为:%p ", ++pc); return 0; }
从数据类型的知识可以知道,在64位windows操作系统中,int占用4个字节,char占用1个字节。 从结果中可以看到,pi+1后,指针的值比之前加了4,而pc+1后,指针的值比之前加了1。(16进制数字加减)
首地址:一段内存空间中第一个存储单元的地址,存储单元。
指针变量的加减:以指针所指向的类型空间位单位进行偏移。
一维数组和指针:
1.定义一个一维数组。数组中的元素依次存放,数组名是这个数组的‘首地址
先定义一个数组int a[10]
a指向a[0] ,a[0] 的类型是int,所以a是int*类型。
a[0]这个地址指向a[0] int(*)[10]元素,字节为4,
&a这个执行整个数组。
如下,数组长度为10。
第一个a+1比a增加了4,
第二个&a+1比&a增加了40(4x10)
#include <stdio.h> #include <stdlib.h> int main() { int a[10]; printf(" a=%d ", a); printf("a+1=%d ", a+1); printf(" &a=%d ", &a); printf("&a+1=%d ", &a+1); }
访问数组元素:
普通下标法:最常见,最基础的,
指针法: 可以使用以下几种,建议*p++,*单目运算符优先级比+双目运算符优先级高,建议加一个’()‘。同时也可以使用*(a+i)来调用数组元素。此时不能用*a++。a是数组名,a++会改变a 。数组名不能改变
#include <stdio.h> #include <stdlib.h> int main() { int a[4]={1,2,3,4}; int *p=a; //p指向a,即a[0] for(int i=0;i<4;i++) { printf(" a[i]=%d ", a[i]); printf("*(p+i)=%d ", *(p+i)); printf(" "); } }
二维数组与指针:
数组名是这个数组的首地址
首地址是一段内容中第一个存储单元
定义一个二维数组int a[3][4],在我们看来,这是一个3x4的矩阵,然鹅在计算机中,依然是线性的,此时
a指向a[0]这个一维数组,类型为int(*)[4], a+1一次加16B
&a指向整个二维数组,类型为int(*)[3][4]
a[0]指向a[0][0]这个int ,类型为int* a[0]+1一次加4B
&a[0]指向a[0]这个一维数组,类型为int(*)[4] (a=&a[0])
二维数组取值
下表法:a[m][n]
指针法:*(a[m]+n)
*(*(a+m)+n) (a先偏移到m行数组,再用*往下走一层,再偏移n个位置,走到a[m][n]的位置,再用一个*取这个地址的值)
多维数组:
从高到低一步一步走下去,
假设一个5维数组 a[][][][][]
变量名 类型
a int(*)[][][][]
a[0] int(*)[][][]
a[0][0] int(*)[][]
a[0][0][0] int(*)[]
a[0][0][0][0] int*
a[0][0][0][0][0] int
善用*的取值和走向下一层和&的取地址功能,明白首地址的意义。可以很快理解各种奇奇怪怪的指针。
注意区分int**这种多级指针和int*[]这种数组指针以及int*[]这种指针数组的区别,同是还有函数指针,结构体指针,字符串与指针,指针形参的函数,
这里我统称他们为自闭指针