day10-指针
预处理:
1.整个编译过程的一个阶段
gcc编译
a.预处理
b.编译
c.汇编
d.链接
2.预处理阶段
预处理命令的执行
#define N
#include
#ifdef //多文件编程 --- 头文件的处理
效果:
文本的替换
int a = 0x1000;
&a; // 0x1000
指针:
1.所谓的指针,其实就是地址,地址是内存单元的编号
(理解:
指针(地址) --作为内存单元的编号,本身也是一种数据
但是这种数据含义比较特殊
2.指针还是一种数据类型,
专门用来,保存,地址(内存单元的编号 )这种数据的,数据类型
指针 ---> 内存单元的编号 ---> 一块内存空间对应
指针变量的定义:
语法:
基类型 * 指针变量名
(1) (2) (3)
(1) 基类型 --- 表示 地址编号 所代表的那块空间 中要存放的数据的类型
//表示的p中存放的 地址编号 所代表的那块空间 中要存放的数据的类型
(2) * --- 表示 定义的变量,不是一般变量,而是指针类型的变量
指针类型变量,意味着这个变量将来是存储 地址 这种数据的
(3) 指针变量名 --- 标识符,命名规则与变量名命名规则一致。
int a;//表示定义了一个int类型的a a是一个变量,一定对应有一块空间
int * p; //表示,定义了一个 指针类型的变量 p
//既然是变量,则p一定有自己的一块空间
//这块空间是用来存放 地址 这种特殊的数据的
//p指向的数据类型是int类型 //p指向的类型 基类型 (目标类型)
//p变量,可以存放的是 int类型的 地址
//指针是一种数据类型, p是什么类型 => int * //int型的指针(地址)类型
int a= 10;
int *p = &a; //从p的角度来看,我们说p指向了 a
float a = 10.1;
int *p = &a; //此时编译会报警,
//因为左右两边类型不一致,p是int * //int * 表示 是一个 存放int类型数据的 空间的地址
//a 是一个float类型的变量, &a 表示获得了这块空间的地址,这个值的类型是float *
通过指针访问数据:
* //单目运算 ---运算数 必须是 地址值
//作用:
// *地址值 =>访问到了地址值对应的那块空间
// eg:
//相当于 酒店中 知道了门牌号 并且 拿到了钥匙,可以进入房间
有了指针之后:
通过a变量名方式访问,这种直接访问
通过p指针方式访问 ,这种间接访问
int a = 10;
int *p = &a;
printf("a = %d\n",a); //直接访问
printf("*p = %d\n",*p); //间接访问
int *p = &; //p的基类型是? int
*p运算:
step1: 首先,拿出p中地址,到内存中定位
step2: 定位到之后,从定位处开始,偏移出 sizeof(基类型) 的一块空间
step3: 将这块空间 当做一个 基类型 的对象(变量)来看
注:
所以 *p 整体相当于是一个基类型的变量
练习:
检测一下你的电脑的大小端?
为什么需要指针?
有些时候,不使用指针,没有办法达到想要的效果!
问题:
一次函数调用,能带出几个结果?
可以实现指针的方式
指针核心用途: (被调修改主调)
使用方法,
1. 要修改谁,就把谁的地址传过去
2. 一定要对其做* 运算
练习:
通过指针的方式,带出两个数的和 和 差
练习:
通过指针的方式,求出两个数的最大值 和 最小值
练习:
实现一个函数,对主函数中两个变量,进行交换
void swap(int a, int b)
{
t = a ;
a = b;
b = t;
}
int main()
{
int m = 10;
int n = 20;
swap(a,b);
}
指针的初始化:
int *p; //
野指针 //指向不明确的指针
int *p = NULL; //NULL 是编号为0的这个地址
-----------------------------------
总结:
1.指针
2.指针变量定义
基类型 * 变量名;
3.指针变量的引用
*指针变量名
*p //理解三步运算过程
4.指针的初始化
如果指针未初始化,此时指针变量中是个随机的地址 --- 野指针
为了让指针变量有明确的值,
int a = 10;
int *p = &a;
int *p = NULL;
5.应用
被调修改主调
使用方法:
a.要修改谁,把谁的地址传过去
b.被调函数中,一定要有 *运算
-----------------------------------
指针 + 数组
指针 + 函数
指针 + 指针
----------------------------------
指针 操作 数组
int a[10] = {1,2,3,4};
指针访问一维数组
*(p+i) <=> a[i] //等价 数组下标访问 本质上就是指针运算
指针的运算:
p+N // 指针+数字
//含义: 表示 指向了 第N个元素的位置
//数值上看,sizeof(基类型)*N
p-N
练习:
找出数组中的最大值
作业:
1.通过指针,实现数组逆序 //写成函数
2.通过指针,实现数组排序 //三个排序
3.C语言100道题 6~10 指法练习
a[0]的类型 int
&a[0] => 取了一块存放着 int型数据的 空间的地址
int *
int *p = a;
*(p+i) <=> a[i] <=>p[i]<=> *(a+i)
void printArray(int a[],int len)
{
}
void printArray(int *a,int len)
{
}
二分查找
指针运算:
&
*
+N
-N
p++ //往后跳了一个元素
p-- //往前
p-q //相同类型的指针 减出的来的结果为 ,地址之间相差的元素个数
关系运算
p > q
p < q
> >= < <= !=
迭代 //从上一个 推到下一个
练习:
以迭代的方式 (指针操作) 的方式,实现数组累加求和
练习:
以迭代的方式,实现数组逆序
b e
1 2 3 4 5 6 7 8 9
b e
1 2 3 4 5 6 7 8 9
|
V
0 1 2 3 4 5 6 7 8 |下标
-----------------------
4 2 6 3 8 1 5 7 8 |数值
^ ^
| |
b e
选择排序:
for (i = 0; i < len-1; ++i)
{
for (j=1+i; j < len; ++j)
{
if (a[i] > a[j])
{
swap(a+i,a+j);
}
}
}
|
V
0 1 2 3 4 5 6 7 8 |下标
-----------------------
4 2 6 3 8 1 5 7 8 |数值
^ ^
| |
b e
冒泡排序:
for (i = len-1; i > 0; --i) //
{
for (j=0; j < i; ++j)
{
if (a[j] > a[j+1])
{
swap(a+j,a+j+1);
}
}
}
------------------------------
|
V
0 1 2 3 4 5 6 7 8 |下标
-----------------------
4 2 6 3 8 1 5 7 8 |数值
^ ^
| |
b e
插入排序:
for (i = 1; i < len; ++i) //
{
int k = a[i];
j = i;
while(j>0&& a[j-1] > k)
{
a[j] = a[j-1];
--j;
}
a[j] = k;
}
------------------------------
|
V
0 1 2 3 4 5 6 7 8 |下标
-----------------------
4 2 6 3 8 1 5 7 8 |数值
^ ^
| |
b e
begin //地址
end //地址
while (begin <= end)
{
mid = (end-begin)/2 + begin;
if ()
{
}else if()
{
}else
{
break;
}
}
return mid // 没找到 NULL
|
V
0 1 2 3 4 5 6 7 8 |下标
-----------------------
4 2 6 3 8 1 5 7 8 |数值
^ ^ ^
| | |
b e m
作业:
1.快速排序
画一下流程图
2.指针操作的算法 再写一下
3.ptc题
5.c语言100道 11~15 指法
----------------------------------
指针操作一维整型数组
1. 迭代的写法
2. 快速排序
a.确定一个基准值 (选择的数组首元素)
b.从右向左 找比基准值 小的值
c.从左往右 找比基准值 大的值
d.交换找到的两个值
e.重复 b ~ d ,直到 begin 与 end相遇 ,
将相遇位置的值 与 基准位置上的值交换
f.针对 比基准值小的一部分 继续快速排序
比基准值大的一部分 继续快速排序
--------------------------------------------
指针操作 一维 字符数组
1. 字符型数组 --- 存放字符串的
char s[] = "hello";
['h' ] <---0x1000
['e' ]
['l' ]
['l' ]
['o' ]
['\0']
//谁能这块空间的地址 --- 数组名
s --->怎么能保存s所代表的地址值
//s数组名 --- 数组首元素的地址 &s[0]
--->地址的类型
char *
char *p = s; //指针变量p 指向了 s (数组)
int puts(const char *s);
注意:
char s[] = "hello"; //s的空间开在 栈上
char *s1 = "hello"; //s1的空间开在栈上,但是s1指向的"字符串常量"
//存放"字符串常量区"
*s1 = *s1 - 32; //不能做 ? 原因是 s1 指向的数据是在 字符串常量区
//常量区的数据不能修改
const int a = 10; //a成了只读变量
const char * s1 = "hello"; //表示 将 *s1 限定为只读
//如果,不希望修改 *s1 的数据
//一般 建议加上const
char * const s1 = "hello"; //const 离谁近,就是限定谁的
char const *s1 = "hello"; //此时 还是 *s1 不能被修改
int *p; //int 代表基类型
//p 本身
char const * const s1 = "hello"; // *s1 和 s1 本身都不能被修改
总结:
1.const 离谁近,就限定谁
2.const 修饰的变量,为只读变量 //变量还是变量 只是成了只读的
实现:
int Puts(char *s)
{
*s = 'a'; //编译时,不报错
}
int Puts(const char *s)
{
*s = 'a'; //编译时,就会报错
}
const char *s // *s 这种方式修改不了数据
形参设计:
什么时候需要加const
1.如果函数内部本身不需要通过*s 修改外面的数据
此时,建议 统统加上const
好处:
1.可以将 错误提前 到 编译时 发现
2.提高了参数的适用性
可以接收数组名
也可以接收 字符串常量 //const char *
练习:
strlen
int Strlen(const char *s)
{
}
size_t Strlen(const char *s)
{
}
练习:
Strcpy(char *dest,const char *src)
char *Strcpy(char *dest, const char *src)
{
char *ret = dest;
...拷贝过程
return ret;
}
返回值:
成功 表示的是dest的首地址
返回值设计成char * 是为了实现 字符串链式操作
char *strncpy(char *dest, const char *src, size_t n);
功能
将src中的前n个字节拷贝到 dest中
注意:
src 长度 > n 此时将前n个字节拷过去,此时dest也不会拷过去'\0'
src 长度 < n 此时将src拷贝完成后,继续拷贝(添加的是'\0')直到完成了n次拷贝
char * Strncpy(char *dest,const char *src, size_t n)
{
//这个函数,拷贝过程,n来控制
}
char src[] = "hello";
strncpy(dest,src,10); //
练习:
strcat
char *strcat(char *dest, const char *src);
功能:
拼接字符串
实现: //返回值 表示的是dest 存放字符串的空间的首地址
char *Strcat(char *dest, const char *src)
{
char *ret =dest;
//1.先定位到 dest 的 '\0'的位置
//2.从dest的 '\0'位置开始,拷贝字符串
//3.最终dest中 需要有 '\0'结束标志
return ret;
}
char *strncat(char *dest, const char *src, size_t n)
功能:
将 src中的前 n 个字符拷贝过去
注意:
始终保证 dest中是一个字符串 ('\0')结束标志的
//使用时,需要确保dest空间足够
char * Strncat (char *dest, const char *src, size_t n)
{
char *ret =dest;
//1.先定位到 dest 的 '\0'的位置
//2.从dest的 '\0'位置开始,拷贝字符串 //受到n的控制
//3.最终dest中 需要有 '\0'结束标志
return ret;
}
-----------------------------------------
Strcmp(const char *s1,const char *s2)
int Strcmp(const char *s1, const char *s2)
{
功能:
从左到右,逐个字符比较
遇到不同的字符时结束 或者 遇到 '\0'
返回 最终不同的字符的差值
}
int strncmp(const char *s1, const char *s2, size_t n);
strncmp("hello","helloa",3); //
int Strncmp(const char *s1, const char *s2, size_t n)
{
功能:
从左到右,逐个字符比较
遇到不同的字符时结束 或者 遇到 '\0' n个比完也停
返回 最终不同的字符的差值
}
----------------------------------------------------------
总结:
指针操作一维数组
思路,
通过指针变量 获得数组所在空间的地址
int a[10];
int *p = a;
*(p+i) <=> a[i]
函数实现的
printArray(int *a,int len)
{
}
Puts(const char *s)
{
}
---------------------------
指针 操作 二维数组
int a[3][4] = {1,2,3,4,5,6,7,8,9};
a 还是首元素的地址
a[0]
a[0][0]
int[4] a[3]; //a数组名 -- &a[0]
a<=>&a[0] //值的角度
a[0] //数据类型 int[4]
&a[0] //int[4] * 指针类型
//标准C语法: int (*)[4]
定义变量:
int (*p)[4]; //数组指针
//基类型 int[4]这种类型 ---数组类型
*p //等价与 a[0]
//相当于是 int[4] 这个一维数组的数组名
*(*(p+0)+0) <=> a[0][0]
*(*(p+i)+j) <=> a[i][j]
//指针操作 一维字符型数组 和 二维整型数组:
char s1[] = "hello";
s1[0] = s1[0]-32;
char *s2 = "hello";
*s2 = *s2 - 32;
const char *s2 = "hello";
char *p;
p = s1;
p = s2;
puts
strlen
strcpy
strncpy
strcat
strncat
strcmp
strncmp
指针操作 二维数组
1.二维数组
c语言中,并不存在真正的二维数组
二维数组本质上,是一维数组的 一维数组
2.指针操作 二维数组
int a[3][4]; //需要一个什么类型的指针变量?
int a[3][4] => int[4] a[3];//元素类型 int[4]
=> //谁能代表数组空间的地址 --- 数组名
=> 数组名 --- 代表的值 是数组首元素的地址, &a[0]
=> a[0]的数据类型 -- int[4]
=>&a[0] --- int[4] * //指针类型
=>c语言中的语法: int(*)[4] //数组指针
int a[3][4];
a[0][0] => int类型
a[0] => int[4]类型
a数组名代表的值 是 数组中首元素的地址 &a[0]
int(*p)[4] = a;
p //int(*)[4]
*p //int降级 //int[4] 是 *p 整体对应的数据类型
//*p对应的值 是 a[0] ---内部的一维数组 数组名 --- &a[0][0] --> int *
*p 降级 (将 行指针 降级成 列指针
&*p
&a[0] //升级 (将 列指针 升级 为 行指针)
*(*(p+i) + j)
0 1 2 3
a[0] | a[0][0] a[0][1] a[0][2] a[0][3]
a[1] | a[0][0] a[0][1] a[0][2] a[0][3]
a[2] | a[0][0] a[0][1] a[0][2] a[0][3]
int a = 10;
int *p = &a;
*p <=> a
作业:
1. 编写程序实现单词的倒置
"how are you" -> "you are how"
//1.整个字符串整体逆序
"uoy era woh"
//2.每个单词再逆序
V
"uoy era woh"
2. 编写一个程序实现功能:
将字符串”Computer Science”赋给一个字符数组,
然后从第一个字母开始间隔的输出该字符串,用指针完成。
Cmue cec
3. c语言100道 题 16~20
//指针操作字符型二维数组
char s[3][10] = {"hello","world","china"};
char s1[] = "hello";
char s2[] = "world"
char s3[] = "china"
char *p1 = "hello";
char *p2 = "world";
char *p3 = "china";
char s[5][10] = {"hello","world","china"}; //二维字符型数组
char *s[ ] = {"hello","world","china"}; //指针的数组 --- 指针数组
char(*p)[10] = s;
练习:
准备三个字符串,找出字符串中最大的字符串
练习:
将二维数组中的字符串逆序
char *s[ ] = {"hello","world","china"}; //指针的数组 --- 指针数组
练习:
从 s数组中找出最大的字符串
练习:
实现逆序
练习:
实现排序
//选择
练习:
查找
总结:
指针数组 // char* s[10]
// int* a[10]
数组指针 int(*p)[4]
---------------------------------------------------------------
函数指针
int add(int a,int b)
{
return a + b;
}
//函数名 --- 代表函数入口地址
函数类型
add函数的类型 ?
把函数名去掉,剩下的是函数的类型
int (int a,int b) //这表示一类函数 --- 返回值为int型,形参为两个int型的函数
注意:
函数指针变量使用时,可以不进行 *p运算
直接使用变量名 + () 传实参,进行函数调用即可
回调函数 (callback)
练习:
写一个程序 实现加,减,乘,除
以回调函数的形式,打印对应的结果
void processData(int a,int b, int (*pfunc)(int,int))
{
printf();
}
输入 一个表达式
1+2
scanf("%d%c%d");
ch
void qsort(
void *base, //表述要排序的数组的起始地址
size_t nmemb, //表示要排序的数组的元素个数
size_t size, //表示要排序的数组的单个元素的大小
int (*compar)(const void *, const void *)//表示 排序 比较的依据
);
void * //万能指针
//万能 表示 可以接收任意类型的 指针
//void 空类型 (无类型)
void *p
*p
练习:
用qsort 对 二维字符型数组排序
作业:
1、封装一个函数,统计一个字符串中出现指定单词的个数
int WordCnt(char *pStr, char *pWord)
{
}
P
"fdsahelfdashellofdashellofdashellofdashellohello"
"hello"
strncmp //
2、编写程序实现将"12345" 转化为12345 (数值)
‘1’ – ‘0’ ? 1
‘1’ ---49
‘0’ --- 48
3.c语言100道题 20~25