一、指针
1、指针概念
a.指针 --- 地址 ---内存单元编号 //值
b.指针 --- 数据类型 ---指针类型 //类型
不同语境:
定义一个指针? //指针类型的变量
打印某个变量的指针? //指针 --地址
2、指针变量的定义
基类型 * 变量名
a.基类型 --- > 表示 指针变量 指向的目标的 数据类型
数据类型
基本数据类型
数组类型
指针类型
b. * //定义时候的 * 表示定义的是一个 指针类型的变量
c. 变量名
eg1:
int a,b,*p,*q; //p和q都是指针类型的变量 *是修饰指针变量名的
int *p,q; //p是指针变量 q int型变量
eg2:
int a = 10;
float b = 1.23;
int *p = &a; //指针变量 p 指向 了 int型变量a
p变量的数据类型 int * //指针类型
&b --地址值 ---类型?
float
b有一块内存空间 -- 放的是float类型的数据
&b ---得到了一块 存放着 float类型数据的 空间的地址
float *p1 = &b;'
float b = 10;
&b --- > 地址值的类型 -->float *
b的数据类型 -- 决定了这块空间放的的数据类型
&b -- 获得了 这块空间 的地址编号
float * //这种类型的地址数据
float *p1 = &b;
注意:
1.指针变量的 大小 64位(8字节) 32位(4字节)
2.指针类型 --- 存储的是 地址 这种特殊的数据
指针变量的给值:
int *p; //野指针 --- 随机值 ---被当做了地址
//避免野指针 -- 一般初始化为NULL --0号地址(不能访问NULL更不能给NULL赋值)
3、通过地址实现对指针变量的间接访问
* 指针运算符
* 单目运算
运算数必须是指针类型的值(地址)
*p 过程
1.首先拿出p指针变量中的值(地址) 到内存中定位
2.从定位处开始,偏移出sizeof(基类型)大小的一块空间
3.把这块空间当做一个 基类型的 变量来看
*p 整体就是一个基类型的变量
eg:
int a = 0;
int *p = &a;
a = 20; //直接访问给变量a赋值20
*p = 20; //间接访问给变量a赋值20
int a = 0x12345678;
char* p = &a;
*p --> 此时只能访问到0x78; //因为从定位处开始,偏移出sizeof(基类型)大小的一块空间
4、核心用途(80%): 被调修改主调
被调修改主调的过程:
1.想修改谁,就把谁的地址传过去
2.必须要做*运算(间接访问),实现修改
扩展:
指针 操作 一维整型数组
int a[10];
int *p = a;
//1.数组本身的特点 (连续性,单一性,有序性)
//2.p+1 --- 偏移了一个基类型
通过指针访问到数组元素:
*(p+i) <=> int型的变量 <=> a[i] <=>p[i]<=> *(a + i)
5、指针运算
&
*
p+1
p++
p-1
p--
关系运算
> >= < <= == !=
p>q
p-q
前提: 同一类型的指针
表示之间差了几个基类型
p+q //指针不能做加法运算
#include<stdio.h>
#if 0
//逆序指针实现
void reverse(int *p1,int *p2)
{
int t = 0;
while(p1 < p2)
{
t = *p1;
*p1 = *p2;
*p2 = t;
p1++;
p2--;
}
}
#endif
#if 0
//二分查找指针形式
void binary_search(int *p1,int *p2,int n)
{
int *mid = NULL;
while(p1 <= p2)
{
mid = p1 + (p2 - p1 + 1)/2;
if(*mid > n)
{
p2 = mid - 1;
}
else if(*mid < n)
{
p1 = mid + 1;
}
else
{
break;
}
}
if(p1 <= p2)
{
printf("1\n");
}
else
printf("0\n");
}
#endif
#if 0
//二分查找递归形式***********************************
int* binary_search(int *p1,int *p2,int n)
{
int *mid = NULL;
int *ret = NULL;
mid = p1 + (p2 - p1 + 1)/2;
if(p1 > p2)
{
return NULL;
}
if(*mid > n)
{
p2 = mid - 1;
ret = binary_search(p1,p2,n);
}
else if(*mid < n)
{
p1 = mid + 1;
ret = binary_search(p1,p2,n);
}
else
{
ret = mid;
}
return ret;
}
#endif
#if 0
//选择排序指针形式
void line(int *begin,int *end)
{
int t = 0;
int* i = NULL;
int* j = NULL;
for(i = begin;i < end;i++)
{
for(j = i+1;j <= end;j++)
{
if(*i > *j)
{
t = *i;
*i = *j;
*j = t;
}
}
}
}
#endif
#if 0
//冒泡排序指针形式
void line(int* begin,int *end)
{
int i = 0;
int len = end - begin;
int* j = NULL;
int t = 0;
for(i = 0;i < len;i++)
{
for(j = begin;j < end;j++)
{
if(*j > *(j+1))
{
t = *j;
*j = *(j+1);
*(j+1) = t;
}
}
}
}
#endif
#if 0
//插入排序,指针实现
void line(int *begin,int *end)
{
int *i = NULL;
int *j = NULL;
int t = 0;
for(i = begin;i <= end;i++ )
{
t = *i;
j = i;
while(j > begin && *(j-1) > t)
{
*j = *(j - 1);
j--;
}
*j = t;
}
}
#endif
#if 0
int main()
{
int s1[10] = {10,9,8,7,6,5,4,3,2,1};
int i = 0;
line(s1,s1+9);
// int n = 0;
// scanf("%d",&n);
// reverse(s1,s1+9);
// line(s1,s1+9);
// int t *ret = binary_search(s1,s1+9,n);
//printf("%p\n",ret); //printf("%d",NALL); --> error
for(i = 0;i < 10;i++)
{
printf("%d ",s1[i]);
}
return 0;
}
二、const前缀
int puts(const char *s);
const char * s;
const int a; //只读变量
int a = 10;
const int *p = &a; //表示 基类型 为只读
p --- 指针变量 --本身的类型 int *
a --- int型变量 --本身类型 int
p = &a;
const int *p = &a;
int const *p = &a; //就近原则 -- 离谁近,就限定谁的
int *const p = &a; //限定p为只读
const int * const p = &a; //p不能被修改,指向的目标类型不能被修改
//(是不能通过*p)
int puts(const char *s)
{
}//const char *s --在函数里面 不能通过*s修改到外面的数据
好处:
1.可以接收 字符数组名 //char *
也可以接收 字符串常量 //const char *
提高了参数的适用性
2.避免了 可能出现的修改的操作
可以将 运行时的错误,提前到 编译时发现
const char * p 可以用来保存字符串常量的地址
作业:
1.实现
strlen
strcat
strcpy
strcmp
#include<stdio.h>
#if 0
int my_strlen(char* p)
{
int count = 0;
while(*p != '\0')
{
count++;
p++;
}
return count - 1;
}
#endif
#if 0
void my_strcpy(char* p1,char* p2)
{
while(*p2 != '\0')
{
*p1 = *p2;
p1++;
p2++;
}
*p1 = '\0';
}
#endif
#if 0
void my_strcat(char* p1,char* p2)
{
int count = 0;
while(*p1 != '\0')
{
p1++;
}
while(*p2 != '\0')
{
*p1 = *p2;
p1++;
p2++;
}
*p1 = '\0';
}
#endif
#if 0
int my_strcmp(char* p1,char* p2)
{
while(*p1 == *p2 && *p1 != '\0' && *p2 != '\0')
{
p1++;
p2++;
}
return *p1-*p2;
}
#endif
#if 0
int main()
{
//char s1[] = "hellloworld";
//char s2[100];
//my_strcpy(s2,s1);
//puts(s2);
//int ret = 0;
//ret = my_strlen(s1);
//printf("%d",ret);
//char s1[6] = "hello";
//char s2[6] = "world";
//my_strcat(s1,s2);
//puts(s1);
//char s1[3] = "aaa";
//char s2[3] = "bbb";
//int i = 0;
//i = my_strcmp(s1,s2);
//printf("%d\n",i);
return 0;
}
2、"编写程序实现单词的倒置 "how are you" -> "you are how"
S1. 整体逆序
"uoy era woh"
S2. 逐个单词逆序
"you are how"
#include<stdio.h>
void reverse(char* begin,char* end)
{
char t = 0;
while(begin < end)
{
t = *begin;
*begin = *end;
*end = t;
begin++;
end--;
}
}
int main()
{
char s1[] = "how are you";
reverse(s1,s1+10);
reverse(s1,s1+2);
reverse(s1+4,s1+6);
reverse(s1+8,s1+10);
puts(s1);
return 0;
}
4、编写程序实现将"12345" 转化为12345 (数值)
#include<stdio.h>
int char_int(char* p1)
{
int i = 1;
int j = 10000;
int sum = 0;
while(*p1 != '\0')
{
i = (*p1 - 48) * j;
sum = sum + i;
j = j/10;
p1++;
}
return sum;
}
int main()
{
char s1[10] = "12345";
printf("%d\n",char_int(s1));
return 0;
}
三、指针操作一维字符型数组(字符串)一般会用到的函数
gets
puts
strlen
strcpy /strncpy
strcat /strncat
strcmp /strncmp
注意:
1.const 能加都加
2.函数功能 尽可能写的全面
char *strncpy(char *dest, const char *src, size_t n)
{
正常拷贝
多了 一个n
n < strlen(src)
只拷贝前n个字符,最终dest中不会有'\0'
n == strlen(src)
正常拷贝
n > strlen(src)
if (n) 拷贝够了次数
剩余拷贝 统统补0
思路:
// 结束条件 *src == '\0'
// n次 拷贝完成没有
}
功能:拷贝指定长度的字符串
char *strncat(char *dest, const char *src, size_t n)
{
拼接的基础上 多 n控制条件
n < strlen(src) 拼n下就结束 n == 0
n >= strlen(src) src拼完就结束 src=='\0'
*dest = '\0' //?
}
功能:在dest所指定的字符串后边拼接指定个数的字符
int Strncmp(const char *s1, const char *s2, size_t n)
{
}
功能:比较指定长度以内的字符串
//hello
//help //3
四、回调函数与函数指针
定义: 通过函数指针调用的函数 叫回调函数
技术上: 通过函数指针的实现
函数指针(指向基类型-为函数类型) 函数类型的指针
eg:
写一个程序 实现加,减,乘,除
以回调函数的形式,打印对应的结果
//用函数回调实现加减乘除
int func0(int a,int b)
{
return a+b;
}
int func1(int a,int b)
{
return a - b;
}
int func2(int a,int b)
{
return a*b;
}
int func3(int a,int b)
{
return a / b;
}
//int (*pfunc)(int,int) 是函数指针,类型是 int (int,int)
//可以理解为是: int (int,int) *pfunc
//C规定书写格式是:int (*pfunc)(int,int)
void processData(int a,int b,int (*pfunc)(int,int))
{
printf("%d",pfunc(a,b));
}
int main()
{
int a = 0;
int b = 0;
char c;
scanf("%d%c%d",&a,&c,&b);
// printf("%d %c %d",a,b,c); //检验scanf是否输入正确
switch(c)
{
case '+':
processData(a,b,func0); // 回调 func0
break;
case '-':
processData(a,b,func1);
break;
case '*':
processData(a,b,func2);
break;
case '/':
processData(a,b,func3);
break;
}
return 0;
}
五、万能指针
void * //万能指针 --可以接收任意类型的指针
//void类型
注意:
如果通过该类型的地址进行数据访问
一定要转换为 明确类型
int compar(const void *a, const void *b) //回调函数
{
*(const int *)a - *(const int *)b
}
int a[10] = {1,2,3,4};
int b[10] = {5,6,7,8};
六、数组指针
1、本质:指向数组的指针
int a[3][4]; //本质还是一维数组
int[4] a[3]; //理解角度
//a --数组名 --代表类型 int [3][4]
//a --代表的值 -- 首元素的地址 -- a[0]
//a[0] 的数据类型 int[4]
//&a[0]--对应的数据类型 int(*)[4] //数组类型 (一维整型数组类型)
//数组类型的指针 --- 数组指针
2、格式:int (*p)[4] = a;
*p //三步运算完成后
*p 相当于 是 int[4]这种类型 //数组
*p 就相当于 int[4]这个数组的 数组名
*(*(p+i) + j)<=>a[i][j]
p+1 //偏移到了 下一个 int[4],因为基类型是int[4],+1偏移一个基类型大小的空间
//类型为int(*)[4]
*(p+1) //偏移到下一个int
//*(p+1) 代表的类型int[4] 此时相当于是 int[4]的数组名
//*(*(p+1) + 1)
练习:
定义一个二维字符数组,找出数组最大值
#include<stdio.h>
{
void maxarr(char (*p)[4],int row) //类型为 char[4] * 代表指针 p 是变量名
{ //理解记忆 char[4]* p
int i = 0;
char max[4];
strcpy(max,*p); //注意要对p进行*操作
for(i = 0;i < row;i++)
{
if(strcmp(max,*(p+i)) < 0)
{
strcpy(max,*(p+i));
}
}
puts(max);
}
int main()
{
char s1[3][6] = {"aaa","bbb","ccc"};
maxarr(s1,3);
return 0;
}
}
七、指针数组
指针数组:存放指针的数组
char** p
char* --> 基类型,表示指向的对象的类型是char *类型
* --> 指针标志
p --> 变量名
**p
*p --> 找到指针数组中所存储的地址元素
**p --> 找到所存地址的所对应的变量
练习:
//指针数组进行排序和查找
#include<stdio.h>
#include<string.h>
void print(char** s1,int n)
{
int i = 0;
for(i = 0;i < n;i++)
{
printf("%s ",*(s1+i));
}
}
void line(char** s1,int n)
//插入排序
{
int i = 0,j = 0;
char* p = NULL;
for(i = 0;i < n;i++)
{
p = *(s1+i);
j = i;
while(j > 0 && strcmp(*(s1+j-1),p) > 0)
{
*(s1+j) = *(s1+j-1);
j--;
}
*(s1+j) = p;
}
}
int binary_search(char** begin,char** end,char* ch1)
{
//二分查找
char** mid = NULL;
while(begin <= end)
{
mid = begin + (end - begin + 1)/2;
if(strcmp(*mid,ch1) > 0)
{
end = mid - 1;
}
else if(strcmp(*mid,ch1) < 0)
{
begin = mid + 1;
}
else
break;
}
if(begin<=end)
{
return 1;
}
else
return 0;
}
int main()
{
char* s1[3] = {"hello","world","china"}; //指针数组 --> char* s1[3]
char ch1[] = "hello"; //字符串常量本身就代表自身的地址
line(s1,3);
// print(s1,3);
printf("%d",binary_search(s1,s1+2,ch1));
return 0;
}
八、函数指针数组
int func0(int a,int b)
{
return a+b;
}
int func1(int a,int b)
{
return a - b;
}
int func2(int a,int b)
{
return a*b;
}
int func3(int a,int b)
{
return a / b;
}
void processData(int a,int b,int (*pfunc)(int,int))
{
printf("%d",pfunc(a,b));
}
#endif
#if 0
//函数指针的数组
int main()
{
int (*P0)(int,int) = func0; //用函数指针来存放函数入口地址
int (*P1)(int,int) = func1;
int (*P2)(int,int) = func2;
int (*P3)(int,int) = func3;
int (*p[4])(int,int) = {func0,func1,func2,func3}; //定义一个函数指针的数组,来存放函数的地址,函数名就代表函数的入口地址
//理解记忆 int (int,int) * p[4]
// 函数类型
int i = 0;
int a = 10;
int b = 3;
for(i = 0;i < 4;i++)
{
printf("%d ",p[i](a,b)); //p[i] 就的等同于 func0,func1 ...;p[0](a,b)-->func0(a,b)
}
return 0;
}
九、类型大综合