是什么?有什么用?用在什么地方?(理解)
养成良好的编程习惯;
培养编程思想;
写代码之前应该先分析需求,分析完需求再开始写代码;(写注释)
1.指针
指针是专门用于保存地址
数据类型 *变量名称;
指针类型的两个用途:
第一个用途, 取值的时候, 会根据指针类型所占用的字节去取出对应字节的数据
第二个用途, 用于做加法运算, 指针+1, 其实是加上指针类型所占用的长度 , 如果当前指针类型是int, 那么+1本质上是加上4个字节
指针没有被初始化不能随便使用, 野指针
只要是数据类型就具备3个特点
1.可以用来定义变量
2.可以用来作为形参的类型
1.可以用来定义变量
2.可以用来作为形参的类型
3.作为返回值
1.1数组指针
概念及定义
数组元素指针:指针变量可以指向数组元素(把某一元素的地址放到一个指针变量中),所谓数组元素的指针就是数组元素的地址.
使用指针引用数组元素
可以使用指针去访问数组.
在指针指向数组元素时,允许++,--
数组指针+1 = 数组下标+1
注意:
数组名就是数组的地址, 数组的地址就是首元素的地址
由于数组的首元素的地址就是数组的地址, 所以定义一个指针指向数组其实就是定义一个指针指向数组的首元素, 所以数组的首元素是什么类型, 那么指向数组的指针就是什么类型
数组名虽然是数组的首地址,但是数组名所保存的数组的首地址是不可以更改的.
指针变量之间的运算:两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数.
只要一个指针指向了数组, 那么访问数组就有3种方式:
1. : array[0];
2. : p[0];
3. : *(p + 0);
1.2 指针和字符串
因为数组名就是数组的地址, 数组名保存的就是数组的第0个元素的地址,所以我们可以使用指针来保存字符串
通过指针保存一个字符串, 其实就是保存的字符串第0个元素的地址
char *str2 = "lmj";
printf("str2 = %s ", str2);
printf("str2 = %s ", str2);
str2[0] = 'm';
printf("str2 = %s
", str2);
通过数组保存字符串和通过指针保存字符串的区别
如果通过数组来保存字符串, 那么字符串是一个变量 str 可以修改
如果通过指针来保存字符串, 那么字符串是一个常量 str2 不能修该
数组保存的字符串存储在内存的栈中, 而通过指针保存的字符串存储在常量区
存储在栈中的变量有一个特点, 当作用域结束系统会自动释放该变量
存储在常量区中的值有一个特点, 不会被释放, 而且多个相同的值对应的地址相同
利用指针保存字符串的应用场景
for (int i = 0; i < 100; i++) {
// 意味着会开辟100块存储空间来保存xmg这个字符串
// 并且会释放100次
// char str5[] = "xmg";
char *str6 = "xmg";
printf("str5 = %s ", str6);
}
// 意味着会开辟100块存储空间来保存xmg这个字符串
// 并且会释放100次
// char str5[] = "xmg";
char *str6 = "xmg";
printf("str5 = %s ", str6);
}
保存字符串的两种方式:
char str[] = "lnj";
存储的位置: 栈
特点: 相同的字符串会重复的分配存储空间
字符串可以修改
char *str = "lnj"
存储的位置: 常量区
char str[] = "lnj";
存储的位置: 栈
特点: 相同的字符串会重复的分配存储空间
字符串可以修改
char *str = "lnj"
存储的位置: 常量区
特点: 相同的字符串不会重复的
1.3指针数组
定义数组的格式: 元素类型 数组名称[元素的个数]
char *names[4] =
{
"lnj",
"lmj",
"jjj",
"lk"
};
for (int i = 0; i < 4; i++) {
printf("names[%i] = %s ", i , names[i]);
{
"lnj",
"lmj",
"jjj",
"lk"
};
for (int i = 0; i < 4; i++) {
printf("names[%i] = %s ", i , names[i]);
}
1.4指向函数的指针
指向函数的指针的定义格式
void (*funtionP) ();
* : 代表是一个指针
funtionP : 代表指针变量的名称, 区分
(*funtionP) : 代表将来指向一个函数
void : 代表将来指向的函数没有返回值
* : 代表是一个指针
funtionP : 代表指针变量的名称, 区分
(*funtionP) : 代表将来指向一个函数
void : 代表将来指向的函数没有返回值
() : 代表将来指向的函数没有参数
应用场景
1.调用函数
2.将函数作为参数在函数间传递
使用注意
由于这类指针变量存储的是一个函数的入口地址,所以对它们作加减运算(比如p++)是无意义的
函数调用中"(指针变量名)"的两边的括号不可少,其中的不应该理解为求值运算,在此处它 只是一种表示符号
2.结构体
基本数据类型: int double float char
构造类型: 数组/ 结构体
人:
姓名: // char *
年龄: // int
身高: // double
构造类型: 数组/ 结构体
人:
姓名: // char *
年龄: // int
身高: // double
数组: 是用于保存一组相同类型的数据
结构体: 是用于保存一组不同类型的数据
要想保存人得数据, 就必须先定义变量
数据类型 变量名称;
如何定义一个结构体变量
1.定义结构体类型
2.根据结构体类型, 定义结构体变量
结构体: 是用于保存一组不同类型的数据
要想保存人得数据, 就必须先定义变量
数据类型 变量名称;
如何定义一个结构体变量
1.定义结构体类型
2.根据结构体类型, 定义结构体变量
2.1定义结构体类型的格式:
struct 结构体类型名称
{
属性;
{
属性;
};
// 1.定义结构体类型
struct Person
{
// char name[20];
char *name;
int age;
double height;
};
// 2.定义结构体变量
// int num;
struct Person p;
// 注意: 数组不能先定义再进行一次性的初始化, 所有下面的写法是错误的
// p.name = "lnj"; // name = {'l', 'n', 'j', ' '};
// 可以使用 结构体变量名称.属性的方式给结构体变量赋值
p.name = "lnj";
p.age = 30;
p.name = "lnj";
p.age = 30;
p.height = 1.75;
2.2 结构体初始化
struct Dog
{
char *name;
int age;
double height;
};
// 1.定义的同时初始化
struct Dog sd = {"wc", 13, 5.0};
// 2.先定义再初始化(逐个初始化)
struct Dog sd1;
sd1.name = "ww";
sd1.age = 5;
sd1.height = 10.9;
// 3.先定义再初始化(一次性初始化)
{
char *name;
int age;
double height;
};
// 1.定义的同时初始化
struct Dog sd = {"wc", 13, 5.0};
// 2.先定义再初始化(逐个初始化)
struct Dog sd1;
sd1.name = "ww";
sd1.age = 5;
sd1.height = 10.9;
// 3.先定义再初始化(一次性初始化)
struct Dog sd2;
// 特别注意: 结构体和数组有一点区别, 数组不能先定义再进行一次性的初始化, 而结构体可以
// 只不过需要明确的告诉系统{}中是一个结构体
sd2 = (struct Dog){"xq", 8, 8.8}; // 数组? 结构体?
// 4.指定将数据赋值给指定的属性
sd2 = (struct Dog){"xq", 8, 8.8}; // 数组? 结构体?
// 4.指定将数据赋值给指定的属性
struct Dog sd3 = {.height = 1.77, .name = "ww", .age = 33};
2.3 结构体的内存细节
1.定义结构体类型并不会分配存储空间
2.只有定义结构体变量才会真正的分配存储空间
3.结构体第0个属性的地址就是结构体的地址
4.和数组一样, 结构体内存寻址从大到小, 存储数组是从小到大(先存储第0个属性, 再一次存储其它属性)
结构体如何开辟存储空间
看上去, 结构体分配存储空间是将所有属性占用的存储空间的总和加在一起后再分配
注意:
其实结构体分配存储空间本质上并不是将所有属性占用的存储空间的总和加在一起后再分配
而是会获取结构体类型中占用内存最大的属性的大小, 然后取该大小的倍数
特例:
如果剩余的存储空间"不够"存储将要存储的数据, 那么就会重新开辟8个字节的存储空间, 并且将需要存储的数据放到新开辟的存储空间中
注意:
其实结构体分配存储空间本质上并不是将所有属性占用的存储空间的总和加在一起后再分配
而是会获取结构体类型中占用内存最大的属性的大小, 然后取该大小的倍数
特例:
如果剩余的存储空间"不够"存储将要存储的数据, 那么就会重新开辟8个字节的存储空间, 并且将需要存储的数据放到新开辟的存储空间中
如果剩余的存储空间"够"存储将要存储的数据, 那么就不会开辟了
2.4 定义结构体变量的方式
int main(int argc, constchar * argv[]) {
1.先定义结构体类型, 在定义结构体变量
/*
struct Person
{
int age;
char *name;
double height;
};
struct Person sp;
struct Person
{
int age;
char *name;
double height;
};
struct Person sp;
*/
2.定义结构体类型的同时定义结构体变量
/*
struct Person
{
int age;
char *name;
double height;
} sp;
// 数据类型 变量名称
sp.age = 30;
printf("age = %i ", sp.age);
struct Person sp1;
sp1.name = "lnj";
printf("name = %s ", sp1.name);
*/
struct Person
{
int age;
char *name;
double height;
} sp;
// 数据类型 变量名称
sp.age = 30;
printf("age = %i ", sp.age);
struct Person sp1;
sp1.name = "lnj";
printf("name = %s ", sp1.name);
*/
3.定义结构体类型的同时定义结构体变量, 并且省略结构体名称
如果在定义结构体类型的同时定义结构体变量, 那么可以省略结构体类型名称
弊端: 由于结构体类型没有名称, 所以以后就不能使用该结构体类型
优点: 如果结构体类型只需要使用一次, 那么可以使用该方式
2.5 结构体的作用域
结构类型定义在函数内部的作用域与局部变量的作用域是相同的
函数外部定义的结构体类型类似全局变量
全局作用域:从定义的那一行开始直到本文件结束为止
如果将结构体类型写在函数或者代码块外面, 那么结构体类型的作用域和全局变量一样, 从定义的那一行开始一直直到文件末尾
相同作用域不能有同名的结构体类型
在不同的作用域中可以有同名的局部变量, 如果访问采用就近原则
在不同的作用域中可以定义同名的结构体类型 , 如果使用同名的结构体类型定义结构体变量, 采用就近原则
如果结构体定义在函数或者代码块中, 那么结构体类型的作用域和变量的作用域一样, 从定义的那一行开始, 一直到函数结束或者到代码块结束。
2.6 指向结构体的指针
如何定义指向结构体变量的指针
1.拷贝结构体类型 和 结构体变量名称
2.在类型和名称中间加上一颗心
当指针指向结构体之后如何利用指针访问结构体
结构体变量名称.属性;
(*结构体指针变量名称).属性;
(*结构体指针变量名称).属性;
结构体指针变量名称->属性;
注意: 报错的原因是因为.运算符的优先级比*高
(*sip).name = "xxx";
(*sip).age = 88;
(*sip).age = 88;
(*sip).height = 1.95;
2.7 结构体数组
// 要求定义变量保存公司中所有部门的绩效
struct Bumen
{
char *name;
int count;
double kpi;
};
struct Bumen ios = {"iOS", 20, 100.0};
struct Bumen andorid = {"Andoird", 10, 99.0};
struct Bumen php = {"php", 500, 88.0};
// 元素类型 数组名称[元素个数];
struct Bumen bumens[3] =
{
{"iOS", 20, 100.0}, // 0
{"Andoird", 10, 99.0},
{"php", 500, 88.0}
};
// bumens[0] == ios
bumens[0].name = "iOSv587";
bumens[0].count = 99;
struct Bumen
{
char *name;
int count;
double kpi;
};
struct Bumen ios = {"iOS", 20, 100.0};
struct Bumen andorid = {"Andoird", 10, 99.0};
struct Bumen php = {"php", 500, 88.0};
// 元素类型 数组名称[元素个数];
struct Bumen bumens[3] =
{
{"iOS", 20, 100.0}, // 0
{"Andoird", 10, 99.0},
{"php", 500, 88.0}
};
// bumens[0] == ios
bumens[0].name = "iOSv587";
bumens[0].count = 99;
bumens[0].kpi = 100.0;
2.8结构体的嵌套
Struct Date{
int month;
int day;
int year;
}
struct stu{
int num;
char *name;
char sex;
struct Date birthday;
Float score;
}
在stu中嵌套存储Date结构体内容
结构体不可以嵌套自己变量,可以嵌套指向自己这种类型的指针
struct Student {
int age;
struct Student stu;
};
如果某个成员也是结构体变量,可以连续使用成员运算符"."访问最低一级成员
struct Date {
int year;
int month;
int day;
};
struct Student {
char *name;
struct Date birthday;
};
struct Student stu;
stu.birthday.year = 1986;
stu.birthday.month = 9;
stu.birthday.day = 10;
2.9结构体和函数
1.将结构体的属性传递给函数, 在函数中修改形参不会影响到实参
2.将结构体名称作为参数传递, 在函数中修改形参不会影响到实参
结构体之间赋值是值传递, 系统会将A结构体的值 拷贝一份到 B结构体中
3.枚举
枚举就是专门用于表示几种固定类型的取值
枚举的本质就是基本数据类型, 就是整形
枚举和结构体一样, 要想定义枚举类型变量, 那么必须先定义枚举类型
枚举类型定义的格式
enum 枚举类型名称
{
取值,
};
取值,
};
// 1.定义枚举类型
enum Gender
{
male = 9, // 男 默认情况下, 枚举的第0个取值就是整数0
female, // 女 第二个取值就是1, 后面的取值递增1
yao // 2
};
enum Gender
{
male = 9, // 男 默认情况下, 枚举的第0个取值就是整数0
female, // 女 第二个取值就是1, 后面的取值递增1
yao // 2
};
2.通过枚举类型定义枚举变量
定义枚举变量和定义结构体变量一样, 直接将数据类型拷贝过来, 空格之后写上变量名称即可
enum Gender sex;
sex = male;
printf("male = %i ", sex);
sex = female;
sex = male;
printf("male = %i ", sex);
sex = female;
printf("female = %i
", sex);
注意点: 由于枚举类型的本质是整型, 所以枚举类型除了可以接收枚举的固定的取值以外, 还可以接收其它整型的值
也就是枚举类型的变量可以当做int类型的变量来使用
1.定义枚举类型
定义枚举类型的规范
枚举类型的取值一般以k开头 后面跟上枚举类型的名称 跟上当前取值的含义
和结构体一样, 枚举类型的名称首字母大写