C语言基础-4

1、指针

        指针全称是指针变量,其实质是C语言的一种变量。这种变量比较特殊,通常它的值会被赋值为某个变量的地址值(p = &a),然后我们可以使用*p这样的方式去间接访问p所指向的那个变量。

1.1、为什么需要指针

        指针存在的目的就是间接访问。有了指针之后,我们访问变量a不必只通过a这个变量名来访问。而可以通过p = &a; *p = xxx;这样的方式来间接访问变量a。

1.2、两种重要运算符:&和*

&        //取地址符,将它加在某个变量前面,则组合后的符号代表这个变量的地址值。
//举例,将变量a的地址值赋值给p。
int a; int *p; p = &a;
a		//代表变量a本身
p		//代表指针变量p本身
&a		//代表变量a的地址值
*p		//代表指针变量p所指向的那个变量,也就是变量a
&p		//代表指针变量p本身的地址值。符号合法,但对题目无意义
*a		//把a看作一个指针,*a表示这个指针所指向的变量。这这里该符号不合法,因为之前已经定义a是个整形变量了。
*           //指针符号。指针符号在指针定义和指针操作的时候,解析方法是不同的。
int *p;		//定义指针变量p,这里的*p含义不是代表指针变量p所指向的那个变量,在定义时这里的*含义是告诉编译器p是一个指针。
int p;		// p是一个整形变量
int *p;		// p是一个指针变量,该指针指向一个整形数使用指针的时候,*p则代表指针变量p所指向的那个变量。

1.3、指针的定义和初始化

        指针既然是一种变量,那么肯定也可以定义,也可以初始化。

//第一种:先定义再赋值
int *p;		            // 定义指针变量p
p = &a;		            // 给p赋值		
		
//第二种:定义的同时初始化
int *p = &a;	        // 效果等同于上面的两句

1.4、各种不同类型的指针

        指针变量本质上是一个变量,指针变量的类型属于指针类型。int *p;定义了一个指针类型的变量p,这个p所指向的那个变量是int型。

int *pInt;				// pInt是指针变量,指向的变量是int类型
char *pChar;			// pChar是指针类型,指向的变量是char类型
float *pFloat;
double *pDouble;
//注:各种指针类型和它们所指向的变量类型必须匹配,否则结果不可预知。

1.5、指针定义的两种理解方法

int *p;	
//第一种:首先看到p,这个是变量名;其次,p前面有个*,说明这个变量p是一个指针变量;最后,*p前面有一个int,说明这个指针变量p所指向的是一个int型数据。

char *(*(*pfunc)[])(char *, char *)  //类似的复杂表达式,可以用相同的分析方法得到

//第二种:首先看到p,这个是变量名;其次,看到p前面的int *,把int *作为一个整体来理解,int *是一种类型(复合类型),该类型表示一种指向int型数据的指针。

//总结:第二种方法便于理解,但是不够本质;建议用第一种方法来理解,因为这种思维过程可以帮我们理解更复杂的表达式。

1.6、指针与数组的初步结合

        数组名:做右值时,数组名表示数组的首元素首地址,因此可以直接赋值给指针。而且数组名是个常量,不能赋值,所以数组名不能做左值。

int a[5];        //则a和&a[0]都表示数组首元素a[0]的首地址。而&a则表示数组的首地址。

        注意:数组首元素的首地址和数组的首地址是不同的。前者是数组元素的地址,而后者是数组整体的地址。两个东西的含义不同,但是数值上是相同的。 

        根据以上,我们知道可以用一个指针指向数组的第一个元素,这样就可以用间接访问的方式去逐个访问数组中各个元素。这样访问数组就有了两种方式:

int a[5];  int *p; p = a;
a[0]	a[1]	a[2]	a[3]	a[4]      //数组的方式依次访问
*p		*(p+1)  *(p+2)	*(p+3)	*(p+4)    //指针的方式依次访问

1.7、指针与++,--符号进行运算

        指针本身也是一种变量,因此也可以进行运算。但是因为指针变量本身存的是某个其他变量的地址值,因此该值进行*, / ,%等运算是无意义的。两个指针变量相加本身也无意义,相减有意义。指针变量+1,-1是有意义的。+1就代表指针所指向的格子向后挪一格,-1代表指针所指向的格子向前挪一格。

*p++;    //就相当于*(p++),p先与++结合,然后p++整体再与*结合。
//*p++解析:++先跟p结合,但是因为++后置的时候,本身含义就是先运算后增加1(运算指的是p++整体与前面的*进行运算;增加1指的是p+1),所以实际上*p++符号整体对外表现的值是*p的值,运算完成后p再加1.所以*p++等同于:*p;   p += 1;

*++p;    //等同于 p += 1;	*p;

(*p)++;  //使用()强制将*与p结合,只能先计算*p,然后对*p整体的值++。

++(*p);  //先*p取值,再前置++,该值+1后作为整个表达式的值。

        总结:++符号和指针结合,总共有以上4种情况。--与++的情况很类似。

1.8、函数传参中使用指针

        int add(int a, int b)    函数传参使用了int型数,本身是数值类型。实际调用该函数时,实参将自己拷贝一份,并将拷贝传递给形参进行运算。实参自己实际是不参与的。所以,在函数中,是没法改变实参本身的。

        C语言函数调用时,一直都是传值调用。也就是说实际传递的一直都是实参的拷贝,但是在用指针传递时,传递的是实参的地址值,这样,让我们在函数中通过间接访问*p的方式,在函数内部访问到了函数外面调用时的实参。所以此时经过变换后,实参的值会发生改变。

2、结构体

2.1、为什么需要结构体

        没有结构体之前,在C语言中,数据的组织依靠:变量+数组。

        最初最简单的时候,只需要使用基本数据类型(int char float double)来定义单个变量,需要几个变量就定义几个。后来情况变复杂了,有时需要很多意义相关的变量(例如需要存储及运算一个班级的学生分数)这时候数组出现了。数组解决了需要很多类型相同、意义相关的变量的问题。        

        但是数组是有限制的。数组最大的不足在于,一个数组只能存储很多个数据类型相同的变量。所以碰到需要封装几个类型不同的变量的时候,数组就无能为力。例如:使用一个数据结构来保存一个学生的所有信息:姓名 学号 性别。这时候就需要结构体。

struct Student
{
    char name[20];        //学生姓名
    unsigned int num;     //学号
    int isMale;           //性别
};                        //这里的分号不能少

//注意:1、结构体类型的定义是在函数外面,不是里面
//2、结构体定义的是一个新的组合类型,而不是变量,也不消耗内存,稍后在定义变量的地方,再使用该结构体类型来定义变量。

int main(void){
    struct Student s1;    //s1是一个变量,类型是struct Student
    //给结构体变量复制
    s1.name[0] = 'J';
    s1.name[1] = 'i';
    s1.name[2] = 'm';
    s1.name[3] = '\0';
    s1.num = 123;
    s1.isMale = 1;
}

2.2、什么是结构体   

        结构体是一个集合,集合中包含很多个元素,这些元素的数据类型可以相同,也可以不相同。所以结构体是一种数据封装的方法。结构体存在的意义就在于,把很多数据类型不相同的变量封装在一起,组成一个大的新的数据类型。

        数据结构:把庞大复杂的数据用一定的方式组织管理起来,便于操作(查找,增加,删除等)这就叫数据结构。

        新增关键字:struct

        新增操作符:.,用来连接结构体和它里面的子元素

2.3、结构体和数组的关联

        数组是一种特殊的结构体,特殊之处在于封装内的各个元素类型是相同的。结构体和数组都是对一些子元素的封装,因此定义的时候都是封装作为整体定义,但是使用的时候,都是使用封装中的子元素。一般结构体变量和数组变量都不会作为一个整体操作。

2.4、使用结构体的步骤

        第一步:定义结构体类型。结构体类型的定义是在函数外面(函数外面 == 全局)的。

        第二步:使用第一步定义的类型来定义结构体变量。

        第三步:使用变量。实际上使用结构体变量的时候,使用的是结构体变量中封装的各个子元素,而不是结构体变量本身。

2.5、结构体的初始化

        结构体变量和普通变量一样,作为局部变量时,如果定义的时候无初始化也无显式赋值,则结构体变量中的子元素的值是随机的。

//定义结构体
struct MyStruct
{
    int a;
    cahr c;
    float f;
    double d;
};
//结构体初始化
struct MyStruct s = {100, 'd', 12.445, 111.111111111};    //ok

struct MyStruct s = 
    {
        //.a = 1444,            
        .c = 'k',
        .f = 3.13,
        .d = 32.333333,
    };                                                    //ok,可以部分初始化

//这种部分ok的是最危险的,千万别用
struct MyStruct s = 
    {
        s.a = 1444,            //只能从前到后一次初始化,中间只要空一个,后面的就会出错,编译还不会报错
        s.c = 'k',
        s.f = 3.13,
        //s.d = 32.333333,
    };

        第一种,完全初始化。{xx, xx, xx, xx, xx};
        第二种,部分初始化。
                {
                    .a = xx,
                    .b = xx,
                    .c = xx,
                    .d = xx,
                };

3、共用体(union,联合,联合体)

        共用体union在定义和使用形式上,和结构体struct很相似。但是两种数据结构是完全不同的两类东西。

//定义共用体
union MyUnion
{
    int a;
    char c;
    float f;
};

int main()
{
    union MyUnion u1;    //使用自定义的union类型来定义变量
    u1.a = 123;
    printf("u1.a = %d, u1.c = %d", u1.a, u1.c);    //u1.a = 123, u1.c = 123

    return 0;
}

        结构体,是对多个数据的组合与封装。

        共用体,共用体中只有一个东西,只是它被好几个名字(和类型)共用。

4、宏定义

4.1、为什么要使用宏定义

        在C语言中,一般使用常数的时候,都不是直接使用,而是先把该常数定义为一个宏,然后在程序中使用该宏名。这样做的好处是,等我们需要修改这个常数时,只需要在宏定义处修改一次即可。而不用到代码中到处去寻找,看哪里都用过该常数。

4.2、宏定义的格式

#define N (321)			//宏定义的格式
#define SEDC_PER_YEAR (365 * 24 * 60 * 60)UL    //使用宏定义定义一年中的秒数,UL是将(365 * 24 * 60 * 60)强制转换成一个无符号长整数

        1、宏定义一般是在函数的外面。

        2、宏定义必须要先定义,再使用宏。如果先使用就会编译报错。

        3、宏定义中宏名一般用大写。不是语法规定的,是一般约定俗成的。

5、枚举

        枚举是列举的意思,枚举是宏定义的一种替代,一种优化。

//定义一个枚举类型,名字叫rnum week
enum week{
    SUN,        //SUN = 0
    MON,        //MON = 1
    TUE,        //TUE = 2
    WEN,        //WEN = 3
    THU,        //THU = 4
    FRI,        //FRI = 5
    SAT,        //SAT = 6 
};

int main(void){
    enum week today;        //使用enum week类型,来定义一个枚举变量today
    today = SAT;

    switch(today)
    {
        case MON:
            printf("好困!\n");
            break;
        ...
    }
    
}

注意:

1、不能有重名的枚举类型。即在一个文件中不能有两个或两个以上的enum被typedof成相同的别名。

2、不能有重名的枚举成员。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值