zoukankan      html  css  js  c++  java
  • 「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 ”,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’}

      };

      内存情况:

       

    二、字符串

    (一)字符串基础

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

      printf(“yang ”);

      其中yang为字符串常量,“yang”=‘y’+‘a’+‘n’+‘g’+‘’。字符串由很多的字符组成,通常使用字符数组来存储字符串,如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)分析代码,了解的作用。

        char name[]=“yang”;

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

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

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

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

        下面是内存情况分析:

         

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

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

      (2)strlen函数

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

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

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

      设

        char name[]=“itcast”;

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

      假设

        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]!=‘’)

        ③. while(str[i])

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

    (三)字符串数组

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

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

      char name2[2][10]={

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

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

      }

      把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
    ",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 ”,*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
    ",*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
    ",*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
    ",sum,minus);
    28 
    29 }
    复制代码

     

    (五)有关指针的疑问

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

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

      int i = 2;

      char c = 1;

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

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

      printf(“c的值是%d ”,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 ”,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 ”,*(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 ”,array[2]);

            printf(“%d ”,*(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 ”,name);//打印结果为yt

     

           *name2 = ‘y’;

          printf(“%s ”,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(“请输入姓名: ”);

          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
    ”,name);
    12 
    13     return 0;
    14 
    15 }
    16 
    17 char *test() // 返回指针的函数
    18 
    19 {
    20 
    21     return “rose”;
    22 
    23 }
    复制代码

     

    (九)指向函数的指针

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

      假设有函数:

      void test ()

      {

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

      }

      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;

    如果一件事情你觉得难的完不成,你可以把它分为若干步,并不断寻找合适的方法。最后你发现你会是个超人。不要给自己找麻烦,但遇到麻烦绝不怕,更不要退缩。 电工查找电路不通点的最快方法是:分段诊断排除,快速定位。你有什么启示吗? 求知若饥,虚心若愚。 当你对一个事情掌控不足的时候,你需要做的就是“梳理”,并制定相应的规章制度,并使资源各司其职。
  • 相关阅读:
    CodeForces 19D Points (线段树+set)
    FZU 2105 Digits Count
    HDU 5618 Jam's problem again(三维偏序,CDQ分治,树状数组,线段树)
    HDU 5634 Rikka with Phi (线段树)
    Java实现 蓝桥杯 算法提高 转圈游戏(暴力快速幂)
    Java实现 蓝桥杯 算法提高 转圈游戏(暴力快速幂)
    Java实现 蓝桥杯 算法提高 转圈游戏(暴力快速幂)
    Java实现 蓝桥杯 算法提高VIP Substrings(暴力)
    Java实现 蓝桥杯 算法提高VIP Substrings(暴力)
    Java实现 蓝桥杯 算法提高VIP Substrings(暴力)
  • 原文地址:https://www.cnblogs.com/wvqusrtg/p/4499346.html
Copyright © 2011-2022 走看看