C语言基础之复杂数据类型的学习

 

    在C语言中的学习中,个人感觉复杂的数据类型这一部分是稍微比较难的部分了,复杂的数据类型主要包括数组,字符串,指针,结构体,枚举,在学习这些知识时会让人深深的感受到C语言的学习中分析内存的重要性了,把这个数据类型在使用时的内存原理分析清楚其实就相当于了解了这些数据类型的使用以及作用,下面我们就来一一好好的学习一下这些"复杂"的数据类型吧。

第一讲     数组

    数组是用来存储一组数据的,只能存放一种类型的数据,比如int类型的数组,float类型的数组,里面存放的数据称为元素。

1. 数组的定义

(1)声明数组的类型      (2)声明数组元素的个数(需要多少存储空间)

2. 数组的格式

元素类型  数组名[元素个数]  比如:int  ages[4]  

3. 数组的初始化

一般情况下数组的初始化是,比如:int ages[4] = {20, 23, 12, 32}; 数组的下标是从0开始

但是在定义数组的时候有些写法比较特殊,下面举例说明一下

 1 #include <stdio.h>
 2 
 3 int main()
 4 {
 5       // 都是正确写法
 6      int ages[5]={19,19,28,27,26};    // 正确的初始化方式
 7      int ages2[5]={19,10};                  // 0和1位置赋值
 8      int ages3[5]={[3]=19,[4]=10};    // 数组中的3和4 的赋值
 9      int ages4[]={10,11,14};
10 
11      // 下面举几个错误写法的例子
12      int ages[];          // 错误写法,未指定元素个数也未指定数组里存储什么值
13      /*只能在定义数组的同时进行初始化,这么写错误
14     int ages[5];
15     ages ={10,11,12};
16      */
17      return 0;       
18 }

 

4. 内存分析

存储空间的划分(内存的分配是从高地址到低地址进行的,但一个数组内部元素又是从低到高进行的)

数组名就代表数组的数组的地址,可以查看元素的地址

如果计算数组元素的个数:可以用sizeof(数组名)/sizeof(数组元素的数据类型)即可得到元素个数

5. 数组作为函数参数传递的问题

当数组作为函数的参数传递时,可以省略元素的个数,比如void change(int array[]);在传递的过程中,涉及到值的传递与地址传递,下面通过代码体会一下:

 

 1 #include<stdio.h>
 2 void change(int array[])
 3 {
 4     printf("array==%p
",array);  //输出数组的地址不用&,数组名就代表数组的地址
 5     array[0]=100;
 6 }
 7 
 8 void change2(int n)
 9 {
10     n= 100;
11 }
12 int main()
13 {
14     int ages[6]={10,11,11,34,23,67};
15     printf("ages==%p
", ages);// 通过打印地址可以更深入了解地址传递
16     
17     //change2(ages[0]); 基本数据类型为函数参数时,纯粹是值传递,数组并未改变
18     change(ages);// 数组名就代表数组的地址,因此此处数组作为函数参数是地址传递,传递的是整个数组的地址,修改函数形参数组元素的                       值,会影响到外面的实参数组
19     
20     printf("%d
",ages[0]);  // 此处输出100
21     
22     return 0;
23 }

 

下面是一个关于数组的综合练习例子,设计一个函数,找出整型数组的最小值

// 设计一个函数,找出整型数组元素的最小值
#include <stdio.h>
/*
 * 定义函数需要两个参数,一个是数组,一个是数组长度
 */
int minOfArray (int array[], int length)
{
    // 定义一个变量存储最小值(默认是首元素)
    int min = array[0];
    // 遍历所有元素,找出最小值
    for(int i = 1; i<length; i++)
    {      
        if(array[i] < min) // 判断当前元素是否小于min
        {
            min = array[i];// 如果当前元素小于min,就用当前元素覆盖min。
        }       
    }
    // 返回最小值
    return min;
}
int main()
{
    // 整型的数组
    int ages[] = {43, 12, 103, 69, 76, 25, 16};   
    int len = sizeof(ages)/sizeof(int); // 计算数组长度   
    int min = minOfArray(ages, len);// 调用minOfArray函数  
    printf("最小值是:%d
", min); // 输出最小值  
    return 0;
}

第二讲     字符串

 字符串是由很多的字符组成的,并且字符是有顺序的,字符串的结束标志是,可以由字符数组来存储字符串。

1. 字符串的初始化

char name[8]="it";  // 默认有个一共三个字节,的ASCII码值是0

char name2[8]={'i','t',''};

char name3[8]={'i','t', 0 };  // 以上这三种写法在内存中的存储情况是一样的

char name5[]={'i','t'};   // 这个不算是字符串(只能说是一个字符数组)

2. 字符串的结束标志""的作用

在字符串中""是结束的标志,程序的字符串输出时也只有遇到""输出才会结束,下面我们通过代码解释一下结束符的作用:

1 void test()
2 {
3     char name[]="it";
4     char name2[]={'o','k'};
5     
6      // name2是一个数组名也是代表其地址,%s会从这个地址开始一个一个的输出字符,直到遇到 为止,因此会输出okit,是字符串结束的标志
7     printf("%s
",name2);
8     printf("%s
",&name2[1]);// 同上 ,此处输出kit,
9 }

定义字符串name时,由于内存寻址由大到小,因此会先分配大的内存地址给name,小的内存地址给name2,而name2没有字符串结束标志,因此输出字符串时直到遇到name的时结束,所以输出结果如上。

3. strlen函数:计算字符串的长度

1>strlen函数的声明在string.h文件中

2>strlen函数计算的是字符串的字符数,并不是字数,一个汉字算作三个字符

3>计算的字符不包括

4>从某个地址开始数字符的个数,直到遇到为止

5>注意跟sizeof的区分,sizeof是计算字节数

下面来通过一个综合的例子来看一下字符串以及strlen函数的使用:

 1 /*
 2  编写一个函数char_contains(char str[],char c),如果字符串str中包含字符c则返回数值1,否则返回数值0
 3  */
 4 #include <stdio.h>
 5 #include <string.h>
 6 
 7 int char_contains(char str[], char c);
 8 
 9 int main()
10 {
11     int result = char_contains("itcast", 'c');  
12     printf("%d
", result);
13     return 0;
14 }
15 
16 int char_contains(char str[], char c)
17 {
18     // 1.遍历整个字符串
19     for(int i = 0;i<strlen(str);i++)
20     {
21         // 如果发现某个字符等于参数c,直接返回1,退出函数
22         if(str[i] == c)
23         {
24             return 1;
25         }
26     }
27     // 2.说明str里面不包含字符c
28     return 0; 
29 }

 第三讲     指针

1. 指针的定义

格式:变量类型 * 变量名; int *p表示定义了一个指针变量p

指针变量只能存储地址

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

指针变量p前面的int:指针变量p只能指向int类型的数据

int *p; 

int a =90; 

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

*p = 10;// 访问指针变量p指向的存储空间,并把10赋值给p指向的存储空间

2. 指针的注意点

(1)  int *p;

         double a = 3.0;

         p = &a;

指针p只能指向int类型的数据(定义指针的类型就指向相应类型的数据)

(2)  int *p;

         p = 10;

指针变量只能存地址

(3) int *p;

        printf(“%d ”,*p);

未经初始化的指针,不能访问存储空间

3. 指向指针的指针

可能初学者接触指针这个概念会有点混淆,对于指向指针的指针就有点更摸不着头脑了,其实只要理解了指针的含义,在考虑清楚想清楚后可以一点点的推敲出来,实在不行的话可以像老师视频教学中一样画一个内存的分析图,很多问题就可以清晰明了了,指向指针的指针表示定义一个指针指向原有的指针,下面通过一段代码体会一下:

#include <stdio.h>
int main()
{
    int a = 10;
    int *p = &a;
    *p = 20; // *p表示指针变量p访问a的存储空间并将20赋值,相当于*p==a==20
    
    int **pp = &p; // 指向指针变量p的指针
    **pp = 20; // 这句实际就是*pp==p  因此**pp==*p==a也就是把20赋值给a
    return 0;
}

 

4. 指针和数组

(1)定义:int ages[5]={10,9,8,67,56}; 

        int *p;

        p = &ages[0]; // 指针变量p指向了数组的首元素,或者写p=ages;数组名就是数组的地址,也是数组首元素的地址

(2)数组元素的访问方式

       a.数组名[下标]  int ages[i];

       b.指针变量名[下标]  p[i]

       c.*(p+i),p--->&ages[0],那么p+1---->&ages[1],p+1是在数组的首地址加一得到第二个元素的地址

(3)指针变量+1,地址加多少取决于指针的类型:

        int * +4; char * +1; double * +8

5. 指针和字符串

上面学习了指针跟数组之间有紧密的联系,而字符串又可以存储在数组中,其实指针跟字符串也是关系密切:

定义字符串的两种方式

a. 利用数组

char name[] = "itcast";

特点:字符串里面的字符是可以修改的

使用场合:字符串的内容需要经常修改

b. 利用指针

char *name = "itcast";

特点:字符串其实是一个常量字符串,里面的字符是不能修改

使用场合:字符串的内容不需要修改,而且这个字符串经常使用

//定义字符串
void test()
{
    char name[] = "it"; // 字符串放在数组里面,可以改里面的元素 
    name[0]='T';  
    //指针变量name2指向了字符串的首字符  
    //字符串常量
    char *name2 = "it"; // char *只能指向一个字符,也就是首字符i
    char *name3 = "it";  
    printf("%c
", *name2); // *name2表示取出name2指向的字符
    
    printf("%s
",name2); // 从name2指向的i向下一个个数直到遇到为止,就相当访问了整个字符串
}

 

6. 指向函数的指针

定义指向函数的指针 函数:double haha(double  d, char *s, int a){}

                                   double (*p)(double , char *, int);

                                   p=haha;

                                   或者double (*p)(double , char *, int) = haha;

通过指向函数的指针可以间接的调用函数:p(),或者(*p)();

下面通过代码来体会一下指向函数的指针分别指向带参数和不带参数的函数,其在调用函数和定义时的一些规范:

void test()
{
    printf("调用了test函数
");
}
int sum(int a, int b)
{
    return a+b;
}
int main()
{
    //定义指针变量指向sum函数
    //左边的int:指针变量p指向的函数返回int类型的数据
    //右边的(int , int):指针变量p指向的函数有两个int类型的形参
    int (*p1)(int, int);
    
    p1 = sum;
    //int c = p1(10,11);
    
    int c = (*p1)(10,11);
    printf("c is %d
",c);
    
    return 0;
}

void test1()
{
    //(*p)是固定写法,代表指针变量p将来指定的肯定是函数
    //左边的void:指针变量p指向的函数没有返回值
    //右边的():指针变量p指向的函数没有形参
    void (*p)();
    
    //指针变量p指向了test函数,函数名就代表函数的地址
    p = test;
    
    p();// 这也是调用test函数,因为p=test
    
    (*p)();//利用指针变量间接调用函数
    
    test();// 直接调用函数
}

 

 

本章学习总结:

     本章在整个C语言的学习中都是非常重要的,数组,字符串和指针是属于C语言的复杂数据类型,其实这三种数据类型又是相通的,只要理解和领会了他们的定义以及在内存中的使用规律,并在平时多加训练和练习,就可以很好的掌握这一块的知识。本章主要学习了数组的定义,格式,以及在其内存分析,需要注意的重点是数组在定义时的规范问题以及数组作为函数参数进行传递时的值传递和地址传递问题,注意数组名就是数组的地址。第二讲主要学习了字符串的相关知识,包括字符串的初始化,以及每个字符串必备的结束标志""的作用,还有计算字符串长度的strlen函数的使用。第三讲主要学习了指针,指针是整个C语言的精华,就像老师所说的,如果指针没有学习好,C语言学得再好也相当于没有学过C语言,可见指针在C语言学习中的重要性,而本章主要学习了指针的定义和使用注意,指向指针的指针,指针和数组,指针和字符串,指向函数的指针等,其中重点需要掌握的是指针和数组以及指针和字符串,这两个知识点相当于把整个本章的知识都整合串联到了一起,要领会指针在使用时的作用和在存储空间里的作用,指针就一个作用:能够根据一个地址值,访问对应的存储空间(包括取值和赋值),理解了这层含义指针就没什么难的。PS:本章知识是C语言重点的重点,需要多加练习,时常复习!

 

原文地址:https://www.cnblogs.com/twcblog/p/4418869.html