嵌入式开发之C基础学习笔记06--数组和指针(非常重要,难点)

非 Y 不嫁゛ 提交于 2020-03-15 21:50:14
C语言进阶学习

数组

1)数组的使用

2)字符串(String):数组(尤其是一维数组)最常用的地方——————>C语言中字符串就是一维数组


数组:

定义:具有相同类型的数据的有序集合,并用唯一的名字来标识。
1)数组必须直接声明,编译器在编译阶段为其分配内存空间
2)C89数组必须是定长的,数组的大小在编译时是固定的;C99允许使用变长数组,数组的大小在运行时确定
 void f(int longeur,int wide)
{
int matrix[longeur][wide];/*定义一个矩阵*/
/*数组的长度由两个参数决定*/
}
3)数组的所有元素占连续的内存空间,在内存中是线性(顺序)存放的,保存数组所需要的内存空间直接与基本类型和数组长度有关。
  数组占用的内存空间 = sizeof(基类型)*数组长度
4)C不检查数组是否越界,程序可以在两边越界。程序员应自己加入越界检查。数组可以越界使用,但是初始化时不允许!
5)向函数传递数组:
  定义数组形参的方法有三种:指针,定长数组,无尺寸数组
  void func1(int *a){...}
  void func2(int a[10]){...}
  void func3(int a[]){...}
  在函数的形参的声明中,数组尺寸无所谓,因为C语言没有边界检查
  实际上,第二种方法在编译后,编译器产生的代码就是让函数接受指针,并不生成10个元素的数组
  A.形参中的数据不能再理解为数组,而必须理解为指针:不能用sizeof求大小;但可以再赋值,这与数组名的指针常量性质不一样。传值时有内容的赋值,但数组内的元素可能有很多,为了避免内容的大量赋值而占用太多的内存,C规定数组传参数就是传指针
  B.int a[][]不能做性参,因为a是指向int[]这样一种数据类型的数组指针,但小表大小没有确定。而int a[][8]可以,并且可以直接用二维数组名(无需显示转换)做其实参[一定要指定一维行参的个数]
6)在处理一个数组的元素时,使用指针自增(p++)的方式通常比直接使用数组下标更快,使用指针能够使程序得以优化
7)C允许定义多维数组,维数上限由编译器定义。但多于三维的数组并不常用,因为多维数组所需要的内存空间对维数呈指数增长。并且计算各维下标会占用CPU时间(存取多维数组元素的速度比存取一维数组元素的速度慢)
8)对数组初始化时注意,C89要求必须使用常量初始化字符,而C99允许使用非常量初始化字符来初始化本地数组


void main()
{
int i,j,min,temp;
int array[10];/*定义一个整型的一维数组*/
printf("please input ten integer:\n");
for(i=0;i<10;i++){
printf("array[%d]=",i);
scanf("%d",&array[i]);
}
printf("the array is");
for(i=0;i<10;i++){
printf("%d",array[i]);
}
printf("\n");
/*排序*/
for(i=0;i<9;i++)
{
min = i;
for(j=i;j<10;j++)
{
if(array[min]>array[j])min = j;
temp = array[i];
array[i] = array[min];
array[min] = temp;
}
}
printf("\nthe result:\n");
for(i=0;i<10;i++)
{
printf("%d",array[i]);
}
printf("\n");
}




字符串:

1)C没有专门的字符串变量,对于它的操作全部由一维数组实现。字符串是字符数组的一种特殊形式,唯一的区别就在于她是作为一个整体操作,而普通数组则不能。最终差别就在末尾的NULL(0)上
2)初始化操作:要使用字符串常量时则把它放到数据区中的CONST区(数据区,全局变量区和静态变量区),用字符串常量初始化字符数组时有一个复制内容的操作,而不仅仅是用一个指针指向他。实际上字符串常量是在常量区还是在堆区,采用何种存储结构,以及是否连续的问题,取决于不同的编译器
3)字符串的输入与输出,下面的函数均由<stdio.h>定义
  A.printf("%s",str);
  B.puts(str);
  C.scanof("%s",str);
  D.gets(str)
4)字符串运算:下面的函数均由<string.h>定义
 strcpy(s1,s2),strcat(s1,s2),strlen(str),strcmp(s1,s2),strchr(s1,ch),strstr(s1,s2)
注意:字符数组,字符指针之间的==比较是地址的比较,结果不可能是true.但字符常量的==比较则不一定。为了避免二义性,应该尽可能用strcmp()来比较
jellonwu @jintao :~/Desktop$ vim test13.c
#include <stdio.h>
#include <string.h>
main()
{
        char a[20] = "hello world";
        //字符串最后一个字符默认会多加一个字符'\0'
        //等价于a[0]='h';a[1]='e'....a[11]='\0'
        char b[20];
        int len;
        len = strlen(a);//字符串长度不包含最后一个字符停止符'\0'
        printf("The string length is %d.\n",len);
        len = sizeof(a);
        printf("The string sizeof vlaue is %d.\n",len);
        strcpy(b,a);
        printf("%s\n",b);
}
jellonwu @jintao :~/Desktop$ gcc test13.c -o test13
jellonwu @jintao :~/Desktop$ ./test13
The string length is 11.
The string sizeof vlaue is 20.
hello world


======================================


指针(Pointer)-----尤其重要

指针是C语言的精华
1)使用指针的好处:
  -->能够内调用函数灵活地修改实参变量的值
  -->支持动态内存分配,能够方便地实现动态的数据结构(如二*数和链表)
  -->可以提高某些程序的效率
  -->实现缓冲方式的文件存取
2)指针是地址。技术上,任何类型的指针都可以指向内存的任何位置。但是指针的操作都是基于类型的。
  指针操作是相对于指针的基类型而执行的,尽管在技术上指针可以指向对象的其他类型,但指针始终认为它指向的是其基类型的对象。指针操作受指针类型而不是她所指向的对象类型的支配


指针表达式:
1)printf("%p",....)/*以宿主计算机使用的格式现实地址*/
2) 指针转换:
 -->void * 型:为Generic Pointer,常用来说明基类型未知的指针
 它允许函数指定参数,此参数可以接受任何类型的指针变量而不必报告类型失配
 当内存语义不清时,也常用于语指原始内存
 例子:一个函数可以返回多个类型(类似于malloc())
 void * f1(void *p)
 {
return p;
 }

 void main(void)
 {
int a = 100;
int * pp;
pp = &a;
printf("%d\n",*(int*)f1(pp));
 }


地址本身就是个整数
指针本身也是个变量


 -->其他类型的指针转换必须使用明确的强制类型转换。但要注意,一种类型的指针向另一种类型转换时可能会产生不明确的行为。
 -->允许将int型转换为指针或将指针转换为int型,但必须使用强制类型转换,且转换结果是已经定义的实现,可能导致非定义的行为。(转换0时不需要强制转换,因为它是NULL指针)
 -->为了与C++更好的兼容,很多C程序员舍弃了指针转换,因为在C++中,必须使用强制类型转换,包括void * 型
3)指针算术:可以用于指针的算术操作只有加法和减法
  -->指针与整数的加,减法
  -->从一个指针中减去另一个指针(主要目的是为了指针偏移量)
4)指针比较:主要用语两个或多个指针指向共同对象的情况


初始化指针:
1)非静态局部指针已声明但未赋值前,其值不确定
2)全局和静态局部指针自动初始化为NULL
3)赋值前使用指针,不仅可能导致程序瘫痪,还有可能使OS崩溃,错误可谓严重之至!
4)【习惯用法】对于当前没有指向合法的内存空间的指针,为其赋值NULL
  因为C保证空地址不存在对象,所以任何空指针都意味着它不指向任何对象,不应该使用它
  用空指针来说明不用的指针基本上就是程序员遵守的协定(但并不是C的强制规则)
  例如:int * p = NULL;
  * p = 1;/*ERROR!*/ 
    /*能通过编译,但对0赋值通常会使程序崩溃*/
jellonwu @jintao :~/Desktop$ vim test14.c
#include <stdio.h>
void main()
{
        int i ;
        char a[] = "I am a student";
        char b[20];
        char * p1, * p2;
        p1 = a; p2 = b;
        //a和b是数组名是一个指针常量,在内存中固定分配,是一个地址,所以没用&符号
        for(;*p1!='\0';* p1++,* p2++)
        {
                *p2 = *p1;
        }
        //p1其实就是a,a是一个维数组,默认最后加了一个'\0'
        //上面的for循环过程为:其实就是遍历数组a中的每一个字符,p1++,其实就是a++就是内存地址+1个字节,也就是下标+1
        //p2[0] = p1[0]对应就是b[0]=a[0],p1++,p2++后再来一次循环,p2[1]=p1[1]对应b[1]=a[1]....直到末尾'\0'结束
        //这一过程实际上就等于是将p1整个数组指针赋给了p2指针
        *p2 = '\0';//加上一个'\0'表示数组结束,如果不加'\0',因为b是20个长度,打印出来剩下的可能都是乱码
        printf("string a is:%s\n",a);
        printf("string b is:");
        for(i = 0;b[i]!='\0';i++)
        {
                printf("%c",b[i]);
        }
        printf("\n");


}
jellonwu @jintao :~/Desktop$ gcc test14.c  -o test14
jellonwu @jintao :~/Desktop$ ./test14
string a is:I am a student
string b is:I am a student


函数指针:
  void process(char *a,char *b,int(*apple)(const char *,const char *))
  /*函数是通过指针调用的,apple是函数指针,传入函数在内存中的首地址*/
  在工作中常需要向过程传入任意函数,有时需要使用函数指针构成的数组。如在解释程序运行时,常需要根据语句调用各种函数。此时,用函数指针构成的数组取代大型switch语句是非常方便的,由数组下标实施调用。
  函数不能直接当作类型作为参数,因为函数本身不是类型,函数指针才是类型
int * f(int * a);-->表示函数返回值是指针类型
int (* f){int * a};--->表示函数指针
例子
#include <stdio.h>
#include <string.h>
void check(char * a,char * b,int (*cmp)(const char *,const char *));
void main()
{
char s1[80],s2[80];
int(* p)(const char*,const char *); /*函数指针*/
p = strcmp;/*将函数strcmp的地址赋给函数指针p*/
printf("输入两个字符串:\n");
gets(s1);/*输入字符串1*/
get(s1);/*输入字符串2*/
check(s1,s2,p);/*通过指针变量p传递函数strcmp的地址*/
}
void check(char * a,char * b,int(* cmp)(const char *,const char *) )
{
printf("测试是否相等:\n");
if(!(* cmp)(a,b))
{
printf("结果:相等\n")
}
else
{
printf("结果:不相等\n")
}


}


指针动态分配(Dynamic Allocation)内存空间:
指程序在运行中取得内存空间。
  全局变量在编译时分配内存空间,非静态局部变量使用桟区,两者在运行时使用固定长度的内存空间
  1)为了实现动态的数据结构,C动态分配函数在堆区分配内存空间。堆是系统的自由内存区,空间一般都很大
  2) 核心函数malloc()分配函数和free()释放函数,使用malloc必须使用free,这个是必须的.
  3) 堆区是有限的,分配内存空间后必须检查malloc()的返回值,确保指针使用前它是非空的。使用malloc()一定要判断malloc返回值进行判断,如果返回值是NULL表示申请内存失败
  4)绝对不要使用无效的指针调用free(),否则将破坏自由表


指针相关问题:
  1)在某些情况下,用const来限制指针对提供程序的安全性有重要意义。大家可以仔细看一下微软编写的系统函数便会有所体会
  比如拷贝字符串这个操作,被拷贝的字符串因为不能被修改,应该加上const
  2)指针的错误难以定位,因为指针本身并没有问题。问题在于,通过错误的指针操作时,可能引入最难排除的错误:程序对未知内存区进行读或写操作
  --> 读:最坏的情况是取得无用数据
  --> 写:可能冲掉其他代码或数据
 这种错误可能要到程序执行了相当一段时间后才出现,因此把排错工作引入歧途
使用指针一定要确定指针指向内存的什么位置
常见错误例子
 例子:未初始化的指针
 int * p;
 scanf("%d",p);/*ERROR*/
 运行小程序时,p中随机地址指向安全区域(不指向程序的代码和数据,或OS)的可能性比较大。但随着程序的增大,p指向重要区域的概率增加,最终使程序瘫痪。


指针与数组:
一维数组名可被不严格地认为是指针常量:可执行指针的操作;可按地址单元赋值和引用;可用来初始化同类型的指针变量;但不能对一维数组名复制。
char p[] = "Hello,World";
char *p = "Hello,world";//指针变量
/*两条语句完全等价*/
1)指针数组:常用于放置指向串的变量,数组中的每一个元素都是指针
  在一些特殊项目中,可能需要把若干个串作为参数传递给函数,但串的长度不能确定,这时采用顶长数组显然不合适
  例子一:给顶错误编号后,输出错误信息
  void print_error(int n)
  {
      static char *error[] = {"Syntax Error\n","Variable Error\n","Disk Error\n"};
      printf("%s",error[n])
  }
  例子二:打印命令行参数,类似于echo命令
  void main(int argc,char **argv, char **env)
  {
while(*++argv) printf("%s",*argv);


  }
  argc --->附加参数的个数,默认包含函数名,什么参数没有argc是1
  argv --->参数具体的字符串数组
  例子三:访问命令行参数中的字符(对argv施加第二下标)
   void main(int argc,char *argv[],char **env)
   {
int i,j;
for(i = 0;i<argc;++i)
{
j = 0;
while(argv[j])
{
putchar(argv[j]);
j++;
}
  printf("\n");
}
   }
 注意:
 --->不要像数组那样按下标单独复制,也不要使用*(p+n)这样的间接引用来修改某个单元的值,这样做都可能引起运行错误,因为字符串常量是在常量区,是不允许被修改的。严格来说,用一个普通指针指向一个常量是不对的。会产生一个cannot overt from const type * to type *的编译错误。字符指针的初始化是一个特列,可用一个字符指针指向一个字符串常量,但仍然不能修改其内容,将const char *强制转换成char *能获得正确的地址,可引用,但仍然不能修改其内容。(将const int *转换成int *则无法获得正确的地孩子,这是字符串常量的特殊性)
 --->按下标或*(p+n)这样的简介引用来读取某单元的内容有时是可行的,这取决于不同的编译器对字符串常量的存放方式:在常量区还是堆区;采用何种存储结构;是否连续(块链就不连续)。而字符数组这样引用和赋值总是可行的,因为它开辟了一块连续的空间并赋值了内容。
2)对于数组a[],输出a,*a,&a的值都是一样,但我们并不能认为含义一样。a有双重含义,只不过通过printf()表现出来的是首元素的地址;*a中a去了其数组指针含义,值为第一个元素的地址;&a中a去了其自定义函数类型含义,值为第一个元素地址。若把a赋给数组指针变量p,则&p是p的地址,因为p并不具备数组这一层自定义数据类型含义。用数组名初始化指针或数组指针时做了自动类型转化,取代了其指针含义。
  也就是说:不要把赋值就理解为一样了,这个等以后学C++后就会有一个比较深入的认识了,C++的运算符重载和自动类型转化隐含了很多东西
3)纯指针数组:即java中定义数组的方法。这样的数组是真正的多级指针,不能用sizeof()求数组大小,但可以实现多维动态,而且存取效率较高(无需做乘法寻址)---这个不常用
4)指向指针的指针:也就是二级指针,也不常用 char * * p
5)动态分配的数组(Dynamically )
例子一:char *p = (char *)malloc(80);
/*必须对p进行下面的测试,以避免使用空指针*/
 if(!p)
 {
  printf("ERROR!\n");
exit(1);
 }
 get(p);
 for(int i = strlen(p)-1;i>=0;i--) putchar(p);
 free(p);
例子二:
 int (*p)[10] = (int(*)[10])malloc(4*sizeof(int));
 /*为了与C++兼容,必须舍弃所有的指针转换*/
 if(!p)
 {
printf("ERROR\n");
exit(1);
 }
  
桟本身就是内存,只不过是特殊的内存

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!