「C」 数组、字符串、指针

 

一、数组

(一)数组

  概念:用来存储一组数据的构造数据类型

  特点:只能存放一种类型的数据,如全部是int型或者全部是char型,数组里的数据成为元素。 

(二)数组的定义

  格式: 类型 数组名[元素个数];

  举例:存储5个人的年龄

  int agrs[5];  // 在内存中开辟4x5=20个字节的存储空间

  可以在定义数组的同时对数组进行初始化:

  int ages[5] = {17,18,19,20,21};

  遍历数组:

  for(int i = 0;i<5;i++)

  {

    printf(“ages[%d]=%d\n”,i,ages[i]);

  }

  注意:

  (1)数组的初始化

    ①. int ages[5] = {17,18,19,20,21}; // 一般写法

    ②. int ages[5] = {17,18}; // 只对前两个元素赋值

    ③. int ages[5] = {[3]=10,[4]=11}; // 对指定的元素赋值,这里为第三个和第四个

    ④. int ages[] = {11,12,13}. // 正确,右边的元素确定,则个数可以省略这里为3个。

    ⑤. int ages[]; // 错误,编译器无法知道应该分配多少的存储空间

    ⑥. int ages[5];ages = {17,18,19,20,21}; // 错误,只能在定义数组时这样进行初始化

    ⑦. int ages[‘A’] = {1,2,3}; // 正确,相当于是ages[65]

    ⑧. int count = 5;int ages[count]; // 如果不进行初始化,则这种写法正确,编译器不会报错为其分配20个字节的存储空间,ages[0]=1;ages[1]=2;可以像这样对数组的元素进行赋值,但是2,3,4等元素的值时不确定的。

    ⑨. 而int count=5;int ages[count]={1,2,3,4,5}; // 这种写法是错误的,在定义数组时对数组进行初始化,元素的个数必须为常量或者不写,不能是一个变量

  (2)计算数组元素

    当没有表明数组元素个数时,如何对其进行遍历(要求使用数组元素个数)?

    可以使用sizeof运算符来计算数组元素的个数

    int count=sizeof(ages)/sizeof(int); // 数组的总长度除以单个的长度等于元素个数

(三)数组内存存储细节

  假设有数组如下:

  int x[] = {1,2};

  char ca[5] = {‘a’,‘A’,‘B’,‘c’,‘D’};

  数组名即代表数组的地址,数组的地址==数组名(ca)==数组的首元素的地址&ca[0]

  在内存中,内存从大到小进行寻址,为数组分配了存储空间后,数组的元素自然的从上往下排列存储,整个数组的地址为首元素的地址。

  模拟该数组的内存存储细节如下:

   

  注意:字符在内存中是以对应Ascii值的二进制形式存储的,而非上表的形式。

  在这个例子中,数组x的地址为它的首元素的地址0x08,数组ca的地址为0x03。 

(四)数组-传址调用

  void change(int array[])  // 数组可以作为函数的形参,可以省略数组元素的个数

  {

    array[0] = 100;

  }

  void change2(int a)  //基本类型作为函数的形参

  {

    a = 200;

  }

  int main()

  {

    int ages[5] = {1,2,3,4,5};

    change2(ages[0]);

    change(ages);

    return 0;

  }

  array数组与ages数组的地址一致,若以数组作为函数的参数,这种传递方式是传址调用,传递的是整个数组的地址,修改形参数组元素的值,就是修改实参的值。

当你把一个数组当做参数来传递时,它会看做是一个指针,在该函数体内使用sizeof运算符来计算数组的长度,得出的数值永远为8,而非数组的实际长度,因为任何类型的指针都占8个字节的存储空间。

  提示:数组作为一个函数的参数时,如果函数体涉及到数组遍历等操作,通常把数组的实际元素个数也作为参数传递给函数。

  如 void maxofarray(int array[],sizeof(ages)/sizeof(int)){....} 

(五)二维数组

  int ages[50]; // 数组能够存放50个int类型的数据

  int ages1[3][10]; // 数组能够存放3个数组,每个数组存放10个数值,共3x10=30个述职数值。

  一个二维数组a,a包括两个一维数组a[0]和a[1],每个一维数组都包括三个元素。

  使用场合:五子棋,俄罗斯方块等,

  假设:

  char Y[3][2]={

  {‘A’,‘B’},

  {‘c,‘D’},

  {‘E,‘f’}

  };

  内存情况:

   

二、字符串

(一)字符串基础

  注意:字符串一定以\0结尾。

  printf(“yang\n”);

  其中yang为字符串常量,“yang”=‘y’+‘a’+‘n’+‘g’+‘\0’。字符串由很多的字符组成,通常使用字符数组来存储字符串,如char name[10] = “yang”;也可以以printf(name);的形式输出,即通过数组来访问字符串,但会有警告。因为默认情况下,printf函数只接受字符串常量作为参数(对变量并未写明)。

  字符串的三种写法:

  1. char name[8] = “yang”;//数组占用了8个字节的存储空间,但是只含有5个字符。
  2. char name[8] = {‘y’+‘a’+‘n’+‘g’+‘\o’};
  3. char name[8] = {‘y’+‘a’+‘n’+‘g’+‘0’};

  这三种写法在内存中的表现都是一样的。

   

  char name[] = {‘y’+‘a’};前面不写个数,不是一个字符串,只能说是一个普通的字符数组。

  char name[] = “yang”;

  name[1] = ‘o’;把字符串的第二个元素值由a改成o。

(二)字符串使用注意点

  (1)分析代码,了解\0的作用。

    char name[]=“yang”;

    char name2[]={‘o’+‘k’};

    printf(“name2=%s”,name2);

    %s:根据右边的参数,打印字符串(遇到\0为止)

    上面代码的打印结果为:okyang

    下面是内存情况分析:

     

    问1:char name[]=“y\0ng”;则打印结果为什么?(oky)

    问2:此时打印name的值,使用%s是多少?Y\0ng还是y?

  (2)strlen函数

    strlen函数计算字符串的长度(字符数)但不包括\0,是字符数不是字数。比如一个汉字占三个字符。

    strlen(“haha”);//长度为4

    strlen(“哈haha”);//长度为7而不是5

  设

    char name[]=“it\0cast”;

    strlen(name);值为2,因为strlen从字符串的地址开始计算,直到遇到\0为止。

  假设

    char name[]=“itcast”;

    char name2[]={‘o’+‘k’};

    int size=strlen(name);

    此时size的值为8。

  (3)练习,编写一个函数char_contains(char str[],char c),如果字符串中包含字符c,则返回1,否则返回0。

    int char_contains(char str[],char c)

    {

      //遍历整个字符串

      for(int i=0,i<strlen(str);i++)

      {

        if(str[i]==c)

        return 1;

      }

      return 0;

    }

    //调用语句

    int result=char_contains(“yang”,‘a’);

    //使用while循环

    ①. while(i<strlen(str))

    ②. while(str[i]!=‘\0’)

    ③. while(str[i])

    ④. int i=-1;while(str[i++])

(三)字符串数组

  二维字符数组,存储两个字符串数组,每个的长度为1,下面是两种写法但存储情况是一样的。

  char name[2][10]={“jack”,“rose”};

  char name2[2][10]={

  {‘j’+‘a’+‘c’+‘k’+‘\0’},

  {‘r’+‘o’+‘s’+‘e’+‘\0’}

  }

  把rose输出:printf(“%s”,name2[1]);

  输出k:printf(“%c”,name2[0][3]);

三、指针

  前导程序

 

 1 #include<stdio.h>
 2 
 3 void change(int *n);
 4 
 5 int main()
 6 
 7 {
 8 
 9     int a = 90;
10 
11     change(&a);
12 
13     printf("a = %d\n",a);
14 
15     return 0;
16 
17 }
18 
19 void change(int *n)
20 
21 {
22 
23     *n = 10;
24 
25 } 

 

(一)基本知识点

  int a = 10;

  int *p; // 定义一个int类型的指针

  p = &a; // 指针变量p指向了变量a

  *p = 20; // 使用指针不通过变量直接修改变量a的值为20

  *p表示访问指针变量p指向的存储空间

  指针一个作用:能够根据一个地址值,访问(取值 | 赋值)对应的存储空间

  指针变量p前面的int,表示指针的类型

    ①. int *p;

    ②. *p = 10;

  两个*的区别:前一个起标识作用,表明定义的p是一个指针,后者的*表示通过访问p指向的地址空间

(二)指针使用注意

  ①. int *p;

    double d = 10.0;

    p = &d; // 不建议此种做法

  ②. int *p;

    p = 200; // 指针变量只能存储地址

  ③. int *p;

    printf(“%d\n”,*p); // 指针变量未经初始化,不要拿来间接访问其他的存储空间

  ④. int *p = &a;但是不能写成 int *p;*p=&a;这种写法没有任何的意义,可以认为*是和类型符一起使用的。

  ⑤. *是指针运算符,访问指针指向的空间

 

 1 #include <stdio.h>
 2 
 3 int main()
 4 {
 5     /* 不建议写法,int *p只能指向int 类型的数据
 6     int *p ;
 7     
 8     double a = 10.0;
 9     
10     p = &a ;
11      */
12     
13     /* 指针变量只能存储地址
14      int *p;
15      p = 200;
16      */
17     
18     /* 指针变量未经过初始化,不要间接拿来访问其他存储空间
19     int *p;
20     
21     printf("%d\n",*p );
22      */
23     
24     int a = 10;
25     //定义变量时的*仅仅是一个象征,没有其他特殊含义
26     int *p= &a;
27     
28     //不正确的写法
29     //*p = &a;
30     
31     //这个时候* 的作用: 访问指向变量p的存储空间
32     *p = 20;
33     
34     char c = 'A';
35     char *cp = &c;
36     *cp = 'D';
37     printf("%c\n",*cp);
38     
39     return 0;
40 }

 

(三)指向指针的指针

  int a = 10;

  int *p = &a; // 指向int型的指针

  int **p1 = &p; // 指向指针的指针

  int ***p2 = &p1; // 三级指针

   

  *p2相当于访问p1;

  **p2相当于访问p;

  ***p2相当于访问a;

  *p1相当于访问p;

  一颗星一条线。

(四)指针练习

  编写一个函数,计算a和b的和与差(一个函数返回两个值)

  提示:指针的作用之一:实现让函数拥有多个返回值

 

 1 #include<stdio.h>
 2 
 3 int SumAndMinus(int n1,int n2,int *n3)
 4 
 5 {
 6 
 7    *n3 = n1 - n2;
 8 
 9    return n1 + n2;
10 
11 }
12 
13 int main()
14 
15 {
16 
17    int a = 10;
18 
19    int b = 11;
20 
21    int sum;
22 
23    int minus;
24 
25    sum = SumAndMinus(a,b,&minus);
26 
27    printf("和 = %d,差 = %d\n",sum,minus);
28 
29 }

 

(五)有关指针的疑问

  注意:任何类型的指针都占据8个字节的存储空间,那么为什么还要为指针加上类型呢?

  对下面一段代码进行内存分析,可以证明指针类型不正确带来的严重后果。

  int i = 2;

  char c = 1;

  int *p = &c; // 本应该是char类型的,写成了int类型

  printf(“c的值是%d\n”,*p); // 打印结果为513,而非1

  printf(“c的值是%d\n”,c); // 值为1

  下面是上述代码的结果的内存分析:

   

  指针p访问的本应该是1个字节空间的数据,此时因为指针的类型是int型的,因此程序自然的从指向的地址0x0a开始读取了4个字节的数据,访问的数据从1变成了513。

  提示:明确了指针的数据类型,指针才能够正确的访问应该访问的空间数据。

(六)指针和数组

  int ages[5] = {10,9,8,7,6};

  遍历数组

  for(int i = 0;i < 5;i++)

    printf(“%d\n”,ages[i]);

  使用指针遍历数组

  int *p;

  p = ages;//也可以写成p = &ages[0];,指针变量p指向了数组的首元素

  元素的地址:

    第一个元素的地址p    &ages[0]

    第二个元素的地址p+1    &ages[1]

    第三个元素的地址p+2    &ages[2]

  元素的值

    *p     ages[0]

    *(p+1) ages[1]

    *(p+2) ages[2]

  把指针当做数组来用:

  for(int i = 0;i < 5;i++)

    printf(“%d\n”,*(p+i));

  (1)数组元素的三种访问形式:

    ①. 数组名[下标]

    ②. 指针变量名[下标]

    ③. *(p+1)

  (2)指针变量的+1究竟是加多少?这取决于指针的类型,如果是char类型则加1个字节,如果是int类型的,则加4个字节。

  (3)利用指针来接收一个数组,指针变量指向了数组的首元素。

    void change(int array[])等价于void change(int *array)。

    前者存储的虽然是数组元素的首地址,但是在传递时就已经变成指针了。

    示例:

      void change(int *array)

      {

        //printf(“%d\n”,array[2]);

        printf(“%d\n”,*(array+2));

      }

      int main()

      {

        int ages[5] = {1,2,3,4,5};

        change(ages);

      }

    调用的结果为:数组的第三个元素3

    若改给change(&ages[2]);则调用的结果为5,因为此时array指向的是ages[2],把ages[2]当做了array的首元素 

(七)指针和字符串

  (1)基础知识

    下面两行代码有着本质的区别:

      ①. char name[] = “it”;

      ②. char *name2 = “it”;//指针变量name2指向了字符串的首字符i

 

      char name[0] = ‘y’;//改变第一个元素的值

      printf(“%s\n”,name);//打印结果为yt

 

       *name2 = ‘y’;

      printf(“%s\n”,name2);//此时程序崩溃

      这是因为,两者一个是字符串变量,一个是字符串常量。c语言的数组元素存放于栈,里面的元素可以随便修改,称为字符串变量。而字符串常量存放于常量区,会缓存起来是不可更改的。

      char *name1 = “it”;

      char *name2 = “it”;

      printf(“%p %p”,name1,name2); // 地址是一样的,说明name1和name2指向的是同一个字符串。

      掌握字符串定义的两种方式:

        ①. 利用数组

          特点:字符串里边的字符是可以修改的,适用于内容需要经常修改时。

        ②. 利用指针

          特点:其实是一个常量字符串,里面的字符不能修改,适用于字符串的内容不需要修改,且这个字符串经常被使用时。

  (2)指针数组

    整型数组:这个数组中存放的都是整型数组

    指针数组:这个数组中存放的都是指针

      int ages[5];

      char *name[5] = {“jack”,“rose”,“yang”}; // 字符串数组的常见写法

    对应于:

      char name2[3][10] = {“jack”,“rose”,“yang”};

    保存字符串数组的两种方式:

      ①. 指针数组(字符串数组)

      ②. 二维字符数组(字符串数组)

    如何输入字符串?(使用数组——因其可变)

    int main()

    {

      char name[20];

      printf(“请输入姓名:\n”);

      scanf(“%s”,name);

      printf(“%s”,name);

    }

(八)返回指针的函数

  程序示例:

 

 1 #include<stdio.h>
 2 
 3 char *test();
 4 
 5 int main()
 6 
 7 {
 8 
 9     char *name = test();
10 
11     printf(“name = %s\n”,name);
12 
13     return 0;
14 
15 }
16 
17 char *test() // 返回指针的函数
18 
19 {
20 
21     return “rose”;
22 
23 }

 

(九)指向函数的指针

  数组名即数组的地址,函数名即函数的地址。

  假设有函数:

  void test ()

  {

    printf(“调用了test函数\n”);

  }

  void (*p)();  // void指针变量指向的函数没有返回值,()表示p指向的函数没有形参

  p = test; // 有指针p,把指针p指向函数

  有三种方式可以操纵函数:

    ①. 直接调用test();

    ②. 利用指针变量简介调用  (*p)();

    ③. 简化使用p()

  练习:

  假设有函数声明为 int sum(int a,int b)

  则相对应的指向该函数的指针应该定义为:int (*p)(int ,int);

  把指针变量p指向函数:p = sum;

  调用该函数的三种方式:

  (1)int c = p(10,12);

  (2)int c = sum(10,12);

  (3)int c = (*p)(10,12);

 

  假设函数声明为:double haha(double a,char *b,int c);

  则定义一个指向haha函数的指针应该为:double (*p)(double,char *,int) = haha;

posted @ 2015-01-10 22:04  Janlor  阅读(483)  评论(0编辑  收藏  举报