目录
一、const 指针和指针const
1.const 指针
const放在*的左边,限制的是指针指向的内容,意思是不能通过指针来修改指针指向的内容,但是指针变量本身是可以修改的。
#include <stdio.h>
int main()
{
// const 指针:就是一个指针,指向一个内存地址
int num1 = 10, num2 = 20;
const int *ptr1 = &num1; // 写法1,const 指针指向一个内存地址
printf("num1 = %d,*ptr1 = %d \n", num1, *ptr1);
int const *ptr2 = &num2; // 写法2,const 指针指向一个内存地址
printf("num2 = %d,*ptr2 = %d \n", num2, *ptr2);
// *ptr1 = 200; // 此时ptr1指向的内存地址中的值不能更改
// *ptr2 = 300; // 此时ptr2指向的内存地址中的值不能更改
int num3 = 100;
ptr1 = &num3;
ptr2 = &num3;
printf("num3 = %d,*ptr1 = %d,*ptr2 = %d \n", num3, *ptr1, *ptr2);
// 总结:const 指针,指向可以改,指向的内存地址中的值不能更改
return 0;
}
2.指针const
const放在*的右边,限制的是指针变量本身,意思是不能修改指针变量的指向,但是可以修改指针指向的内容。
#include <stdio.h>
int main()
{
// 指针 const:就是一个常量,存储了一个内存地址
int num1 = 10, num2 = 20;
int *const ptr1 = &num1; // 常量ptr1 存储了变量的内存地址
printf("num1 = %d,*ptr1 = %d \n", num1, *ptr1);
// ptr1 = &num2; // 会报错 此时ptr1指向的内存地址不可更改的.
*ptr1 = 100; // ptr1常量指向的内存第中的值是可以修改的
printf("num1 = %d,*ptr1 = %d \n", num1, *ptr1);
// 总结:指针 const,指向的内存地址不可以更改,指向的内存地址中的值可以进行更改
return 0;
}
二、多级指针
指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
多级指针的定义与使用
1.声明多级指针时,需要使用多个星号来表示指针的级别。
int num = 10;
int *ptr1; // 一级指针
int **ptr2; // 二级指针
int ***ptr3; // 多级指针
2.初始化多级指针时,你需要逐级给指针赋值,确保每个级别的指针指向正确的目标。
int num = 10;
int *ptr1 = # // 一级指针指向了num变量的内存地址
int **ptr2 = &ptr1; // 二级指针指向了ptr1指针变量的内存地址
int ***ptr3 = &ptr2; // 三级指针指向了ptr2指针变量的内存地址
3.解引用多级指针时,需要根据指针的级别使用适当数量的星号解引用操作。
printf("num = %d \n",num);
printf("*ptr1 = %d \n",*ptr1); // 解引用一次
printf("**ptr2 = %d \n",**ptr2); // 解引用两次
printf("***ptr3 = %d \n",***ptr3); // 解引用三次
三、空指针
赋为NULL 值的指针被称为空指针,NULL 是一个定义在标准库 <stdio.h>中的值为零的宏常量。
声明指针变量的时候,如果没有确切的地址赋值,为指针变量赋一个NULL 值是好的编程习惯。
四、野指针
野指针就是指针指向的位置是不可知(随机性,不正确,没有明确限制的)。
指针使用前未初始化
指针变量在定义时如果未初始化,其值是随机的
,此时操作指针就是去访问一个不确定的地址,所以结果是不可知的。此时p就为野指针。
int *p;
printf("p = %p,*p = %d \n", p, *p);
在没有给指针变量显式初始化的情况下,一系列的操作(包括修改指向内存的数据的值)也是错误的。(该指针不在咱们受控的范围,如果在单片机上访问其他真实物理地址,数据不受保护)
指针越界访问
int arr[3] = {10,20,30};
int *p = arr;
p += 3;
printf("%d \n",*p); // 此时*p就是越界访问
当 p += 3之后,此时 *p
访问的内存空间不在数组有效范围内,此时 *p
就属于非法访问内存空间,p为野指针。
指针指向已释放的空间
#include <stdio.h>
int *test(){
int a = 10;
return &a;
}
int main()
{
int *p = test(); // 此时p指向的是释放的空间
printf("%d \n",*p);
return 0;
}
调用test()函数将返回值赋给p,test函数的返回值是局部变量a的地址,函数调用结束局部变量会被销毁,其所占用的内存空间会被释放,p 指向的是已经释放的内存空间,所以 p 是野指针。
注意:
- 指针初始化如果没有确切的地址赋值,为指针变量赋一个 NULL 值是好的编程习惯。
- 小心指针越界。
- 避免返回指向局部变量的指针。
- 指针使用之前检查指针是否为 NULL。
变量定义 | 类型表示 | 含义 |
int i | int | i是一个整型变量。 |
int *p | int * | p 是一个指向整型数据的指针 |
int a[5] | int[5] | a 是一个5个元素的整型数组。 |
int *p[4] | int *[4] | p 是一个指针数组,每个元素都是指向整型数据的指针。 |
int (*p)[4] | Int (*)[4] | p 是一个数组指针,指向一个4个元素的整型数组。 |
int f() | Int () | f是一个返回整型数据的函数 |
int *f() | int *() | f是一个指针函数,返回指向整数数据的指针。 |
int (*p)() | int (*)() | p 是一个函数指针,指向一个返回整型数据的函数。 |
int **p | int ** | p是一个指向指针的指针。 |
五、枚举
语法格式
枚举使用 enum
关键字来定义,枚举名称和枚举元素名称都是标识符,定义一个枚举就是定义了一种枚举数据类型,语法如下:
enum 枚举名称
{
枚举元素1,
枚举元素2,
...
枚举元素N
};
枚举元素也称为枚举成员或枚举常量,具有如下特点:
- 枚举元素的值必须在同一枚举中是唯一的
- 枚举元素的值必须是整数类型,通常是int
- 如果没有为枚举元素指定值,编译器会自动为它们分配值,从0开始,依次递增。
- 定义枚举的时候也可以为枚举元素自定义值,需保证唯一性和整型类型。
enum Weekday
{
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
};
// 定义枚举类型,标识三原色。 给枚举元素RED设置了值1,后面的枚举元素从1开始自增,分别是 1到3
enum Color
{
RED = 1,
GREEN,
BLACK
};
// 定义枚举类型,表示北京地区一年四季的平均温度。为每个枚举元素都设置值,自己设置的值需要保证唯一性和整型类型
enum Season{
SPRING = 15,
SUMMER = 37,
AUTUMN = 25,
WINTER = -10
}
如果枚举常量的值是连续的,我们可以使用循环遍历;如果枚举常量的值不是连续的,则无法遍历
六、结构体
C语言提供了struct关键字,允许自定义复合数据类型,将不同类型的值组合在一起,这种类型称为结构体(structure)类型。
C语言没有其他语言的对象(object)和类(class)的概念,struct 结构很大程度上提供了对象和类的功能。
格式:
struct 结构体名
{
数据类型1 成员名1;
数据类型2 成员名2;
……
数据类型n 成员名n;
};
声明结构体变量
方式1 先定义结构体,然后再创建结构体变量
//先定义结构体
struct Student
{
int id; // id
char name[50]; // 名字
char gender; // 性别
int age; // 年龄
};
// 定义结构体变量
struct Student stu1, stu2;
方式2 在定义结构体的同时定义结构体变量
//定义结构体的同时定义stu1和stu2变量
struct Student2{
int id;
char name[50];
char gender;
int age;
} stu1, stu2;
方式3 在定义时也可以不给出结构体名
// 没有结构体名字
struct{
int id;
char name[50];
char gender;
int age;
} stu3, stu4;
结构体指针
声明结构体指针的语法格式:
struct 结构体名字 *结构体指针变量名;
通常需要先定义一个结构体变量,再创建结构体指针,并取结构体变量的地址作为结构体指针的值。
#include <stdio.h>
int main()
{
struct Student // 定义结构体
{
int id; // id
char *name; // name
int age; // age
};
// 定义结构体变量
struct Student stu;
// 定义结构体指针并初始化
struct Student *ptr = &stu;
return 0;
}
通过结构体指针访问成员
结构体指针可以通过“->” 操作符访问结构体的成员。
“->” 成员访问符
#include <stdio.h>
int main()
{
// 定义结构体
struct Student{
int id;
char *name;
int age;
};
// 定义结构体变量并初始化
struct Student stu = {118, "jack", 18};
// 通过结构体变量访问成员
printf("id=%d,name=%s,age=%d \n",stu.id,stu.name,stu.age);
// 定义结构体指针变量并初始化
struct Student *ptr = &stu;
// 通过结构体指针变量解引用访问结构体成员
printf("id=%d,name=%s,age=%d \n",(*ptr).id,(*ptr).name,(*ptr).age);
// 通过结构体指针->访问结构体成员
printf("id=%d,name=%s,age=%d \n",ptr->id,ptr->name,ptr->age);
return 0;
}
总结:如果指针变量p指向一个结构体变量stu,以下3种用法等价:
- stu.成员名
- (*p).成员名
- p->成员名
案例
- 编写一个Dog结构体,包含name(char *)、age(int)、weight(double)属性。
- 编写一个say函数,返回字符串,方法返回信息中包含所有成员值。
- 在main函数中,创建Dog结构体变量,调用say 函数,将调用结果打印输出。
#include <stdio.h>
// 定义结构体Dog
struct Dog{
char *name; // 名字
int age; // 年龄
double weight; // 重量
};
char * get_gog_info(struct Dog dog){
static char info[50]; // 局部静态变量,存储信息数据
sprintf(info,"name=%s,age=%d,weight=%.2f",dog.name,dog.age,dog.weight);
// 修改名字
dog.name = "大黄";
return info;
}
int main()
{
// 定义结构体变量
struct Dog dog = {"旺财", 5, 5.78};
// 定义接收信息的字符指针变量
char *info = NULL;
// 调用函数并接收结果
info = get_gog_info(dog);
printf("狗狗的信息:%s \n",info);
printf("main中狗狗的名字:%s \n",dog.name);
return 0;
}
总结:
区分三个概念:结构体、结构体变量、结构体变量的成员。
结构体是自定义的数据类型,表示的是一种数据类型。结构体变量代表一个具体变量。类比:
int num1; // int 是数据类型, 而num1是一个具体的int变量
struct Car car1; // Car 是结构体数据类型,而car1是一个Car变量
Car就像一个“汽车图纸”,生成出来的具体的一辆辆汽车,就类似于一个个的结构体变量。这些结构体变量都含有相同的成员,将结构体变量的成员比作“零件”,同一张图纸生产出来的零件的作用都是一样的。