二、C语言高级
指针、构造类型、动态内存管理、多文件与关键字
1、指针
是直接访问内存的方式
内存地址:在内存空间中,对每一个字节都分配了一个唯一的编号,用于区别不同的位置。每个字节都有唯一的地址,用来和内存中的其他字节相区别
指针:在内存空间(定义变量)申请空间来的存储地址,就叫做指针,也叫做指针变量
指针就是操作内存地址,使用内存地址
1.1、定义指针变量
指针变量:本质就是一个变量,存放的是一个内存地址
指针变量定义的形式:
指向数据类型 * 指针变量名;
指向数据类型:存储哪种类型数据的地址,指向哪种类型
指针变量:定义一个变量,用于存储地址,如果要存储哪种类型的地址,数据类型就是对应的类型
1.2、指针的使用
存储地址:
指针变量 = 地址(要求地址的空间能够使用)
变量的地址获取:通过 & 取地址运算符,得到变量的地址
指针变量 = &变量
指针变量访问内容:操作对应地址的数据,通过地址的方式访问数据
用变量名来表示代表变量中的值
用指针来访问变量:
*指针名 访问对应地址(指针存储的地址)的数据空间
int * p;
p = &a;
*p ; // ==== a 表示访问 p存储的地址(即a的地址),对应空间的空间
指针变量赋值地址:通过指针建立与对应内存空间联系
指针 取*:得到建立联系的内存空间的值
指针变量的初始化:
在定义指针变量时,进行赋值,就叫做初始化
类型 * 指针名 = 地址;
野指针:
指针记录的地址不明确(存储的地址不知道,或地址对应空间是否具有操作权限不确定),这种指针叫做野指针
野指针不需要直接进行 取 *
空指针:
指针记录的地址是NULL(地址:0x00),系统规定NULL地址不允许进行操作,只要操作就会报错
空指针通常表示,该指针当前不可用
万能指针:
void * 类型指针变量,可以存储任意类型的地址,也可以把指针赋值给其他的任意类型指针变量
void * 指针,指针存储地址,不能进行 取值操作(取 *),因为指向的类型未知不明确
1.3、指针的运算
指针 + / - 整数,表示移动指针 指向 的位置
+:
指针 + 整数n
往地址增大方向,移动 n 个 指向(指针指向,指针存储哪种地址)的数据类型 大小
int * p;
p + 5 ==== >移动 5 * 4
指针 + n == 指针 + n * sizeof(指向类型)
-:
指针 - 整数
往地址减小方向,移动指针指向类型(指针存储哪种类型地址)的大小 * 整数大小
指针 - 整数 * sizeof(指向类型)
指针++:
先使用指针,然后 指针 = 指针 + 1
++指针:
先 指针 = 指针 + 1 ,然后再使用指针
指针--,--指针同理
指针 - 指针:
获取两个指针之间间隔多少个对应类型的数据
(指针 - 指针) / sizeof(指向类型)
1.4、指针与一维数组
一维数组是一段连续的空间存储多个数据元素,在数组中相邻的元素,间隔大小为每个元素类型的大小,即 &数组名[元素i] == &数组名[元素i-1] + 类型大小
指针能够进行运算,指针 + 1,移动一个数据类型的大小,即 指针 + 1 == 指针 + 指向类型大小
如果指针变量存储了数组中的元素地址,指针+1 ,就是数组中下一个元素的地址
指针与数组的操作方式:
可以通过指针访问整个数组
只要知道数组的起始地址(第零个元素的地址),就可以遍历整个数组
数组首地址:数组的起始地址,就是第零个元素的地址
int * p = &a[0];
*(p+n) == a[n]
在数组中,数组名 有个特殊作用。数组名就表示 数组的首地址
数组名,地址常量
int a[5];
a == &a[0]
数组首地址(数组名) + n:偏移n个数据元素大小
*(数组名 + n) == 数组名[n]
*(a + 3) == a[3]
基于数组名(常量地址),可以将数组名当做指针进行使用,除了不能赋值运算(a = a+1)
因为数组名可以表示数组首地址,而指针变量也可以存储数组首地址,在访问数组的操作时,指针变量和数组名作用一致,所以
数组名[n]:访问数组的n元素 ==== 指针名[n]
int *p,a[5];
p = a;
p + n == a + n//等价
a[n] == *(p+n) == *(a+n) == p[n]
基于指针变量,可以将指针当做数组用,不能越界
字符串与字符数组
字符串:有多个字符组成一个连续且有序的字符序列
"abcdef"------字符串
C程序中,通过字符数组来存储字符串
字符数组通过访问数组中的元素,就是访问字符串(按照字符数组,每个元素单独访问)
如果需要整体访问字符数组中存储的字符串,要求在字符串结束的下一个字符位置存储'\0'
'\0'字符就表示 字符串的结束
字符数组存储字符串:
字符数组初始化存储字符串
char数组名[大小] = "字符串";
注意通常,在进行输入前把数组初始化全为'\0'
输入字符串到字符数组中
scanf("%s",数组名/首地址);
输出打印字符数组中的字符串
printf("%s",数组名);
常量字符串表示:
"acbde"-----常量字符串
在常量字符串中,在最后字符后默认包含'\0'字符
在程序中如果写出常量字符串,则常量字符串表示常量字符串的首地址(第零个字符地址)
如:“abccde”,得到就是字符串的首地址,地址常量
地址,指针类型,都是统一的大小,各个类型之间没有区别
32位机器:4B
64位机器:8B
练习:
1、在数组中有10个数,统计10个数的和(通过指针)
2、输入一个名字,计算是否存在 'x'字符(指针)
作业:
1、输入一个字符串,求字符串的长度
2、输入两个字符串到两个数组中,然后把两个字符串进行拼接
1.5、指针与二维数组
二维数组:二维数组中,每个元素是一个一维数组,在元素(一维数组)中,每个成员就是一个值
1.5-1、二维数组:
数据类型数组名[行][列]
行:有多少个一维数组
列:一维数组的元素个数
对于二维数组而言,数组名是整个二维数组的首地址,第零个元素的地址,二维数组的元素都是一维数组,即二维数组数组名表示其中元素,整个一维数组的地址。
int a[3][4];
a == &a[0];//a[0]是整个一维数组
由于a表示整个元素的地址(一维数组的地址),所以进行指针运算时,+1 ,加上 整个一维数组大小
a:&a[0],第零个一维数组的地址
a+1:&a[1],第一个一维数组的地址
a+2:&a[2],第二个一维数组的地址
a[0]:表示二维数组的元素零,第零个一维数组,a[0]是一维数组的数组名,在这个一维数组的首地址
a[0] == &a[0][0]
a[0]+1 == &a[0][1]
a[1]:第一个一维数组,也是这个一维数组的数组名(首地址)
a[1] == &a[1][0]
a[1]+1 == &a[1][1]
注意:
a+1,表示移动二维数组的一个元素(一维数组)大小
a[0]+1,表示移动一维数组的一个元素(数据类型值)大小
1.5-2、数组指针:
是一个指针,指针用于存储整个数组的地址,指针的指向类型为数组
定义:
数组元素类型 (* 指针变量名)[大小];
int (*p)[4];//定义一个指针变量,指针变量存储 整个数组的地址(int [4])
数组指针和二维数组名是等价的,因为二维数组表示第零个元素(一维数组)的地址
int a[3][4];
int (*p)[4];
p = a;//&a[0]
p+1 == a+1 == &a[1]
*(p+1) == a[1]
*(p+1)+2 == &a[1][2]
*(*(p+1)+2) == a[1][2]
1.6、多级指针与指针数组
1.6-1、指针数组:
是一个数组,只是每个元素为指针类型
指向类型 * 指针数组名[元素个数];
int * p[5];
定义 包含 5个元素 每个元素为指针(int *)
int a;
p[2] = &a;
1.6-2、多级指针:
一级指针:存储变量的地址
二级指针:存储一级指针的地址:一级指针类型 * 指针名;
三级指针:存储二级指针的地址:二级指针类型 * 指针名;
......
指向类型 * 指针名;
作业:
1、定义一个字符二维数组,输入要存储‘#’号的个数,然后分别输入存储的位置,其他位置默认存储 ‘ ’空格字符
2、二维数组和数组指针
int a[2][3];
int (*p)[3] = a;
在以下每个表达式中,p = a,求表达式的含义
a[0]
a[0][2]
*p
**p
*a
**a
*p++
**(p+1)
*(p+1)+1
*(*(p+1)+1)
3、定义二维数组,输入数据,然后求每个元素中的最大值
4、字符串的替换,输入一个原字符串,然后输入要替换的起始位置,输入新字符串进行替换
1.7、指针与函数
指针作为函数的参数:可以表示变量的地址,或者是数组名/数组首地址,作用就是表示参数为地址
把地址作为参数进行传递
返回值类型 函数名 (指针参数1,指针参数2); 接受传递的是地址
调用:
函数名(地址,地址/指针);
如果是数组作为形式参数,会由编译器自动变为对应类型指针
int sumarray(int p[10],int length) p就是指针 int*
指针作为函数返回值:返回一个地址,把指针作为函数的结果返回
函数指针:指针存储是的函数的地址
函数的类型表示:
返回值类型(参数1,参数2,参数3,......);
int (int a,char b);-1
int (int a,int b);-2
int (int c,int d);-3
1和2是不同函数类型,2和3是相同函数类型
函数指针表示:
返回值类型(* 指针变量名)(参数1,参数2,参数3,......)
函数地址:
函数名就是用于表示函数的地址
函数指针 = 函数名;
调用函数:
通过函数指针进行调用
函数指针名(实参);
1.8、构造类型
由于基本数据类型不能满足需要,需要把多个的数据类型进行组合,共同表示新的复合数据,形成新的类型。构造新的数据类型的方式就叫做构造类型
1.8-1、结构体
使用多种数据类型作为成员,进行组合,构成的新的类型,就叫做结构体
声明结构体:结构体的类型表示
struct结构体名字
{
类型1 成员1;
类型2 成员2;
类型3 成员3;
....
};
在程序中添加一种新的类型,不占用内存空间,只是说明在程序中有一中新类型
定义结构体变量:
struct结构体名字 变量名;
对结构体变量的操作,就是对结构体变量的成员进行操作
结构体变量访问成员:
结构体变量名 . 成员名;
结构体变量初始化:
在定义结构体变量时,对其成员进行初始化
顺序对成员初始化:
struct结构体名变量名 = {成员1值,成员2值,成员3值,......};
指定成员初始化:
struct结构体名变量名 = { .成员名2 = 成员值,.成员名4 = 成员值,......};
结构体指针如何访问变量的成员:
结构体指针:
struct结构体名* 指针名 = 结构体地址;
访问方式:
指针->成员://访问结构体指针对应地址中的成员
(*指针).成员 == 指针->成员
结构体特殊的声明方式:
(1)
struct 结构体名字
{
类型1 成员1;
类型2 成员2;
类型3 成员3;
....
}结构体变量名;--------在声明时同时定义结构体变量
(2)
struct
{
类型1 成员1;
类型2 成员2;
类型3 成员3;
....
}结构体变量名;--------在声明时同时定义结构体变量,但是之后不能使用这个声明进行定义变量
1.8-2、共用体(联合体)
使用多种数据类型作为成员,进行组合,但是使用同一段空间存储(多个成员共用一个空间),构成的新的类型,就叫做共用体
使用共用体的问题:同一时刻只能操作一个成员
声明共用体类型:
union 共用体名
{
类型1 成员1;
类型2 成员2;
类型3 成员3;
....
};
用法:共用体与结构体一致
1.8-3、枚举
枚举就是定义一种新类型时,这个类型能够取值的范围是确定的,通过这个定义的新类型把能欧取值的范围一一列举出来,这种类型就叫做枚举
声明枚举类型:
enum枚举名
{
成员1,
成员2,
成员3,
......
};
枚举类型中,每个成员就代表能够取的一个值
声明类型时,如果成员没有赋值,成员就等于上一个成员的值+1,如果成员1没有赋值则为0
定义枚举变量:
enum 枚举名变量名;
变量名 = 成员;
1.8-4、字符串函数
#include <string.h>
//把src首地址的字符串,拷贝到dest首地址中
char *strcpy(char *dest, const char *src);
//把src首地址的字符串前n个,拷贝到dest首地址中
char *strncpy(char *dest, const char *src, size_t n);
//比较字符串s1 和 s2 是否相等,如果相等返回0,如果不等返回当前字符的差值
int strcmp(const char *s1, const char *s2);
//比较字符串s1 和 s2 的 前n 个字符 是否相等,如果相等返回0,如果不等返回当前字符的差值
int strncmp(const char *s1, const char *s2, size_t n);
//计算 s 字符串的字符个数(不算'\0'),返回值就是字符串的长度
size_t strlen(const char *s);
//把src字符串 拼接到 dest字符串后
char *strcat(char *dest, const char *src);
//把src字符串 前n个字符 拼接到 dest字符串后
char *strncat(char *dest, const char *src, size_t n);