<10>数组
1) 为什么需要数组?
解决大量同类型数据的存储和使用,模拟现实世界(二维数组,三维数组用来模仿平面,3D空间)
2) 一维数组
作用是为n个变量连续分配存储空间,所有变量的数据类型相同,所占字节大小相等
对于一维数组的几个补充点:
初始化操作,只有在定义数组的同时才能整体赋值
比如:int a[5]={1,2,3,4,5}是可以的; int a[5]={1,2,3}也是可以的,未赋值的自动为0;
Int a[5]={0}也是可以的,所有的元素都是0;int a[5]也可以,但是每个成员都是垃圾值。
而int a[5];a[5]={1,2,3,4,5};是错误的。
数组的名字是数组第一个元素的地址
比如说int a[5]={1,2,3,4,5}; a也就是&a[0]
3) 二维数组
格式方面,举个例子就是int a[3][4];就是3行4列的数组
初始化方面,int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}
4) 多维数组
这只是一种逻辑上的存在,在pc物理上并不存在。因为内存是一维线性的,n维数组实际上是多个n-1维数组的组合,n-1维数组实际上又是n-2维数组的组合,最终都会化为1维数组。
5) 关于两个一维数组的赋值问题
int a[3]={1,2,3};
int b[3];
int i;
for(i=0;i<3;++i)
{
b[i]=a[i];
}
<11>指针
1) 指针和指针变量
指针是地址,是内存空间的编号,本质上是一个操作受限的非负整数;操作受限是指它只能是同一份连续的内存空间的不同存储单元时相减,意义是判断两个地址相隔几个单元
指针变量是变量,这个变量存储的是地址。
2) 一个指针变量占几个字节数
char * p;int *q;double *r;这三个指针变量所占字节数一样吗?答案是一样的,都是4个字节。为什么一样?实际上,p,q,r存储的,只是各个数据类型的首个字节的地址。比如char占一个字节,那么p存的就是这个字节的地址。int占4个字节,double占8个字节,q就是这四个字节1+3中的1,也就是第一个的地址,r就是1+7的那个1的地址。
那么既然都是存的是第一个字节的地址,为什么指针变量本身的大小为什么是4个字节呢?这和内存大小有关。我们这里说的4个字节,是建立在内存大小为4G的基础上。4G就是2的32次方,那么如果用二进制来访问所有的bit,就需要32位的二进制代码也就是地址总线需要32根,也就是4个字节来描述地址,或者说地址大小是4个字节。而pqr存储的都是地址,所以就要求变量能存储4个字节,或者说占4个字节。
上文提到的,指向第一个字节,是怎么确定整个数据的呢?比如说int *,它存的是int的首个字节的地址,按理说不应该只找得到第一个字节吗,其他三个字节怎么办呢。这就说到了为什么是int *,这个中的int,就能告诉pc。从我指定的这个字节开始,一共要4个字节。所以也就是,int *指向整个int。
3)指针的重要性
表示一些复杂的数据结构
比如链表,比如树都需要指针的参与
快速地传递数据
因为在函数的调用时,使用指针可以只需要传递变量首位地址(4个字节),并不需要完全复制变量然后传入
使函数返回一个以上的值
在被调函数中通过指针,形参就可以修改主调函数中实参的值
能直接访问硬件
因为指针代表的是硬件的地址
能够方便处理字符串
理解面向对象语言里引用的基础
4)举个例子来认识指针
int main(void)
{
int a=1;
int * p=&a;
}
p就是指针变量,它的类型是int *类型,这控制了它只能存储int类型变量的地址。p保存了a的地址,意为p指向a。p不是a,a也不是p。修改p不会影响a,修改a也不会影响p。*是&的逆运算,也就是从地址找变量。*p也就等同于变量a。
5)多级指针
int i=10;//i存放整数
int *p=&i;//p存放整数变量的地址
int **q=&p;//q存放,存放整数变量地址的变量 的地址
int ***r=&q;
6)关于指针可能会带来的内存泄漏和野指针的问题
内存泄漏
比如 int *p;没有初始化。那么在程序中,变量p是可以访问的,因为p的空间是为这个程序分配的,所以p可以访问。但是p里面是一个垃圾值,*p,就是按照这个垃圾值为地址去找它的对应变量。那么,这块垃圾值地址,并不是分配给这个程序的。所以,*p的访问非法。
野指针(针对动态分配)
就比如说。假如p,q,r都是指针变量,都指向同一个变量a。那么,要释放a这块内存空间的时候,只能free一次,比如说free(p)。free了之后,这块空间就不再属于这个程序了,如果再次进行free,比如free(q),就会报错。而q,r还指向这块不属于这个程序的内存空间,q,r也就成了野指针。
6) 形参和实参为什么一定不同
比如有一个函数int swap(int i,int j),主函数里面调用了这个函数,并且传入实参,假设是swap(a,b)。你以为是把a,b送给了swap用了,实际上不是的。在执行这个语句的时候,实际上的情况是,swap函数重新申请两块内存空间,一块是i,一块是j。所谓的传入,是把a,b的值。copy一份给i和j。swap的操作,一直是针对这个i,j进行的,而不是a,b。而swap结束的时候,系统又收回i,j的空间。i,j消失,下次调用时候,再重新再搞两个内存空间给i,j。而对于内存的申请,分配到的一定是空闲单元,i,j申请到的一定不会是a,b的空间。所以,i,j和a,b一定不同。所以形参和实参一定不同。
8)为什么指针可以使被调函数修改主调函数
比如说 int swap(int *i,int *j),然后在主函数里面,调用函数swap(&a,&b)。注意这次传入的是&a,&b。是a和b的地址。那么在swap函数执行的时候,虽然i,j仍然是两块新开的内存空间。但是这块空间里面,存储的是a和b的地址。在函数进行操作的时候,只需要*i,*j,就可以访问主调函数里面的a,b,也就是对变量a和b进行了操作。
9)指针和数组
一维数组的数组名是一个常量指针,存放的是一位数组第一个元素的地址,换句话说,数组名是个指针。举例,对于数组int a[5];int * p=a; p[i],*(p+i),a[i]是等价的。
10)函数处理一个数组时,需要的参数问题
2个参数,数组名和数组长度。数组名告诉你首个元素的地址,数组长度告诉你什么时候停。
<12>动态内存分配
1) 传统数组的缺点
数组长度必须事先指定,且长度不能更改
数组的内存程序员不能手动释放,只能在函数运行完毕时,由系统自动释放
不能跨函数使用
A函数定义的数组,在函数运行期间可以被其他函数使用。但是A函数运行完毕后,A函数中的数组将无法被其他函数使用
2) 为什么要动态数组
很好的解决这些缺陷
3) malloc函数用法
int *p=(int *)malloc(200)
malloc的作用是,请求系统分配字节,并返回第一个字节的地址。(int *)指明这第一个字节的地址是int *类型,这200个字节4个4个地划分,也就是50个变量。如果是(char *),那就是200个变量,如果是(double *),那就是25个变量。
这句代码总共分配了204个字节,p是4个,200是200个动态内存单元。p变量本身的内存是静态的,函数终止时自动释放,p指向的内存是动态分配的,可以用free(p)释放掉。
*p就是一个整形变量,但是这个变量动态分配的,单单的int p是静态分配的。
4) 动态一维数组的构造
静态:int a[5]。实际上是分配了20个字节,4个4个地形成一个int。
动态:int *p=(int *)malloc(sizeof(int)*len)
sizeof的功能是返回int类型的所占字节数,因为平时说4个字节,实际上不同的pc会有区别。malloc(sizeof(int)*len),创建了长度位len的动态数组,所需的字节数为sizeof(int)*len。
数组名是p,长度是len,每个元素是int类型,类似于int p[len]
5) realloc用法
负责数组的扩充和缩小,比如int *p=(int *)malloc(200);realloc(p,100),就是把p数组从200字节缩小为100字节。
6) 动态内存和静态内存区别
动态内存是程序员手动分配和释放,在堆中分配。
静态内存由系统自动分配和释放,栈中分配。
因为动态内存不在栈中,就不需要出栈和压栈这些,所以就可以跨函数使用
7) 跨函数使用内存
静态分配的变量不能跨函数使用
void f(int **q)
{
int i=5;
*q=&i;
}
int main(void)
{
int *p;
f(&p);
printf(“%d\n”,*p);//非法,因为函数f执行完,i已被释放,而仍然使用*p访问,则非法
return 0;
}
动态分配变量可以跨函数使用
void f(int **q)
{
*q=(int *)malloc(sizeof(int));
**q=5;;
}
int main(void)
{
int *p;
f(&p);
printf(“%d\n”,*p);//合法,因为是动态分配的变量**q
return 0;
}
总的来说,就是说动态分配的变量不会随函数的终止而被回收空间。
函数运行结束之后要出栈,动态变量是在堆里存储的,所以*q这块地址空间仍能访问。
<13>结构体
1) 举例
struct Student
{
int age;
float score;
char sex;
};//定义了一个数据类型,叫做struct Student
2) 为什么需要结构体
为表示一些复杂的事物,而普通的基本类型无法满足实际需要
3) 什么是结构体
把一些基本类型组合起来,形成一个新的复合数据结构,叫做结构体
4) 结构体的初始化
struct Student st={80,66.6,‘F’}//st是变量名
struct Student st1;st1.age=10;st1.score=88;st1.sex=‘F’;
定义的同时可以整体赋值,定义完只能单个赋值。
5) 如何取出结构体里的成员
除了上面的结构体变量名.成员名,比如st1.age;
还可以指针变量名->成员名,比如struct Student st={80,66.6,‘F’}; struct Student *pst;pst=&st;就可以使用pst->age,相当于(*pst).age,st.age。
6) 函数调用为什么建议传入地址,而不是传入内容
传入地址只需要用到第一个字节的地址编号,更快也可以减少内存的耗费。传入内容要完全复制一个变量。
7) 结构体变量的运算
只能相互赋值,比如struct Student st1,st2;st1=st2;是正确的。
8) typedef
也就是取个别名
typedef struct Student
{
int age;
float score;
char sex;
}St,*pSt;
等价于St代表了struct Student,pSt代表了struct Student *。
9) 枚举
把一个事物所有可能的取值一一列举出来
举例:enum Weekday:{Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday}
定义了一个数据类型,名字叫enum Weekday并没有定义变量。但是变量只能在这7个值里面选择。
默认Monday是1,Tuesday是2。enum Weekday day=Tuesday是对的,但是不能定义成enum Weekday day=2。这样赋值的内核是,物理上存储按照2来存储,但是逻辑上是Tuesday。
只是printf(“%d”,day)输出的是2。但是枚举传参时候,可以使用1,2,3,4来代表。
比如说enum Weekday val=Tuesday;switch(val){case 2: printf(“是星期二”)},这个时后case可以接2。输入Tuesday,可以正常输出。
enum Weekday:{Monday=4,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday}
这个时候就是4,5,6,7,8
<14>算法
算法是解题的方法和步骤
1) 狭义
对存储数据的操作,不同的存储结构,所执行的算法是不同的,算法依附于数据结构
2) 广义
广义的算法叫做泛型,对数组实现的操作,也可以套用到链表。具体逻辑相同,实现方法不同,泛型是处理逻辑的,有点像伪代码的意思?
标签:字节,--,int,地址,分配内存,数组,变量,指针 From: https://www.cnblogs.com/fjcz/p/16967712.html