标签:return 语言 int char ++ printf 第四章 指针
第四章 指针
4.1 指针的概念
操作系统会给每个存储单元分配一个编号,0x 00 00 00 00 ~ 0x ff ff ff ff,这个编号就是地址,地址就是指针,指针变量就是用来存储地址的变量。
在 32 位系统下,指针的大小都是 4 个字节,64 位系统下是 8 个字节。
4.2 指针的定义和使用
#include <stdio.h>
int main()
{
int a = 10;
/*
定义一个指针变量来保存 a 的地址
步骤:
1. 需要保存谁的地址, 将其定义形式放在此处 int a
2. 定义一个和 * 结合的符号结合 *p
3. 使用 * 和符号替换掉定义形式里面的变量名或数组名或函数名 int *p
*/
int *p = &a; // p 保存了 a 的地址, 也可以说 p 指向 a, & 在这里是取地址符号
/*
1. 只要在定义处看到符号和 * 结合, 这个符号一定是一个指针变量
2. 指针变量的类型: 将符号去掉剩下的类型 int *
3. 指针变量保存什么数据类型的地址: 将符号和符号最近的一个 * 去掉剩下的类型 int
*/
//指针的使用: 在使用时对指针变量取 * 得到的是取这个指针变量指向那块内存空间的值
printf("%d\n", *p);
*p = 100;
printf("%d\n", *p);
printf("%d %d\n", sizeof(p), sizeof(int *)); // 测量指针变量和指针类型的长度
return 0;
}
/*
在使用时, 对一个表达式 &, 会使表达式类型加一级 *
在使用时, 对一个表达式 *, 会使表达式类型减一级 *
定义时则相反
例如:
int *p = &a; 这条语句是定义变量 p, 和使用变量 a, 所以 p 的类型是 int *, a 现在的类型也可看成 int *
*p = 100; 这条语句是在使用变量 p, p 的类型就相当于 int, 因此可以将 100 赋值给 *p
int *p, *q; 定义两个指针变量 p q
int *p, q; 定义了一个指针变量 p 和整型变量 q
*/
4.3 指针的步长
概念:
- 宽度:取指针变量指向空间的内容,取的是字节数
- 步长:指针变量 +1 跨过的字节数,等于宽度
- 步长 = sizeof(将符号和符号最近的一个 * 去掉剩下的类型)或者sizeof(符号和符号最近的一个 *)
int main()
{
double a = 5;
double *p = &a;
printf("sizeof(p) = %d, sizeof(*p) = %d\n", sizeof(p), sizeof(*p));
}
大小端(字节序):
数据以字节为单位存储在内存中,必然存在一定的顺序,这个顺序就是大小端。
- 大端:低位存高地址,
- 小端:高位存高地址。
#假设从左至右内存地址增大
0x01020304 # 01 是高地址, 04 是低地址
---> 01 02 03 04 # 大端
---> 04 02 03 01 # 小端
int main()
{
int num = 0x12345678;
int *p1 = #
short *p2 = (short*)#
char *p3 = (char* )#
printf("%x %x %x\n", *p1, *p2, *p3);
return 0;
}
4.4 多级指针
int main()
{
int a = 5;
int *p = &a; // p 是一级指针, 保存 a 的地址
int **q = &p; // q 是二级指针, 保存 p 的地址
int ***j = &q; // j 是三级指针, 保存 q 的地址
//a = *p = **q = ***j
printf("%p %p %p\n", p, q, j);
printf("%p %p %p\n", &a, &p, &q);
printf("%d %d %d\n", *p, **q, ***j); // 5 5 5
return 0;
}
4.5 特殊指针
4.5.1 野指针
int main()
{
//野指针: 未初始化的指针
int *p; // p 的指向是随机的 指向的内存也是随机的
printf("%d %p\n", *p, p); // 无法取值, 但是可以取地址
int *q = (int *)0x1000; // 存在 但不建议这样使用
int *j = (int *)malloc(10); // 允许 向内存申请一个连续的空间
int a;
int *k = &a; // 允许
free(j); // 使用了 malloc 申请空间一定要释放掉
return 0;
}
/**
* @header #include <stdlib.h>
* @brief 向堆区申请连续的空间
* @param Size: 申请的空间大小
* @return 申请成功返回地址, 失败返回 NULL
*/
void *malloc(size_t Size);
/**
* @header #include <stdlib>
* @brief 释放申请的空间
* @param Memory: 被释放空间的地址
*/
void free(void *Memory);
4.5.2 空指针
int main()
{
int *p = NULL; // 用来作为一个标志 如果这个指针等于 NULL 代表没有被使用
int a = 5;
p = &a;
printf("%d\n", *p);
*p = 100;
printf("%d\n", *p);
return 0;
}
/*
定义指针是赋值为 NULL
使用时不为NULL
使用结束后,赋值为 NULL
通过判断指针是否为 NULL, 判断这个指针是否还在使用
*/
int *fun ()
{
int a = 5;
return &a;
}
int main()
{
int *p = fun();
printf("%p\n", p);
printf("%d\n", *p);
}
4.5.3 万能指针
int main()
{
int a = 10;
char b = 20;
short c = 30;
void *pa = &a; // void * 是万能指针, 可以匹配其它类型的指针
void *pb = &b;
void *pc = &c;
*(int *)pa = 200;
*(char *)pb = 10;
return 0;
}
4.5.4 const 修饰指针
int main()
{
const int a = 10;
int b = 0;
//const int *p = int const *p
int const *p = &a; // 不能修改 a 的值, 但本身的值可以修改
int * const q = &a; // 可以修改 a 的值, 但本身的值无法修改
int const * const j = &a; // 不能修改 a 的值, 本身的值也无法修改
int **k = &q;
*q = 20;
*q = (int *)0x100;
printf("%p\n", q);
printf("%d\n", a);
return 0;
}
4.6 指针修饰函数
//指针修饰形参
void swap (int *x, int *y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
//指针修饰返回值
char *my_strcat(char *DstStr, const char *SrcStr)
{
char *str = DstStr;
while (*DstStr)
DstStr++;
while (*SrcStr)
*DstStr++ = *SrcStr++;
return str;
}
int main()
{
int a = 10;
int b = 20;
swap(&a, &b);
printf("a = %d b = %d\n", a, b); // a = 20 b = 10
return 0;
}
4.7 指针与数组
/*
指针数组: 存放指针的数组, 数组的每一个元素都是指针类型
通过运算符的优先级, 先读低优先级名称, 再读高优先级名称,
例如: *p[2] 指针数组, (*p)[2] 数组指针
*/
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int b[5] = {6, 7, 8, 9, 10};
int *p[2] = {a, b};
/*
一级指针相当于一维数组相当于字符串
二级指针相当于二维数组相当于一维指针数组
一维指针数组也能表示一维数组
*/
//int *p[2] = {&a[0], b};
int **q = p;
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 5; j++)
{
//printf("%d ", *(*(q + i) + j));
printf("%d ", q[i][j]);
}
}
return 0;
}
/*
数组指针: 存放数组地址的指针
*/
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int (*p)[5] = &a;
//指针的类型 int(*)[5], 保存的数据是 a[5] 的地址
printf("%d ", sizeof(p));
printf("%p %p\n", p, p + 1); // sizeof(int[5])
//对数组指针取 * 不是得到整个数组的内容, 而是得到首元素地址
//如果将 p 看成一个行地址, 对行地址取 * 得到的是该行的第 0 列的地址
for (int i = 0; i < 5; i++)
{
printf("%d ", *(*p + i));
}
return 0;
}
int main()
{
int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
int (*p)[4] = a;
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
for (int j = 0; j < sizeof(a[0]) / sizeof(a[0][0]); j++)
{
//printf("%d ", *(*(p + i) + j));
printf("%d ", p[i][j]);
}
printf("\n");
}
return 0;
}
/*
数组作为函数形参会被退化为指针
*/
void print_array (int *x, int n)
{
for (int i = 0; i < n; i++)
{
//printf("%d ", *(x + i));
printf("%d ", x[i]);
}
}
void print (int (*p)[3], int row)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < sizeof(*p) / sizeof(**p); j++)
{
//printf("%d ", *(*(p + i) + j));
printf("%d ", p[i][j]);
}
}
}
int main()
{
int a[5] = {1, 2, 3, 4, 5};
print_array(a, sizeof(a) / sizeof(a[0]));
int b[2][3] = {1, 2, 3, 4, 5, 6};
print(b, sizeof(b) / sizeof(b[0]));
return 0;
}
4.8 指针的运算
- 相同类型的指针相减得到的是两个指针之间的步长数;
- 指针可以自加和自减;
- 指针与指针之间不可以相加;
- 指针可以比较:p == q p > q。
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int *p = a;
int *q = &a[3];
printf("p = %p ", p);
printf("q = %p\n", q);
printf("q - p = %d\n", q - p);
printf("q == p + 3 为 %d\n", q == p + 3);
printf("++p = %p\n", ++p);
//printf("p + q = %p\n", p + q); // C 语言的 + 不能计算指针类型
return 0;
}
4.9 字符数组与指针
int main()
{
char str[128] = "hello world"; // 字符串
char *p = str;
/*
char str1[128] = "hello";
char *str2 = "hello";
str1 是一个常量, str2 是一个变量
str1 的内容存在栈区, 允许被修改, str2 的内容存在常量区, 不允许被修改
str1 的大小为 128, str2 的大小为 4
*/
p = p + 6;
printf("%s\n", p); // world
printf("%d\n", sizeof(p)); // 4
printf("%d\n", strlen(p)); // 5
*(p + 2) = 'W'; // p[2] = 'W'
printf("%s\n", p); // woWld
printf("%s\n", str); // hello woWld
return 0;
}
int main02()
{
char *str = "hello world";
str = str + 6; // str是指针变量 可以改变指向
printf("%s\n", str);
//*(str + 2) = 'W'; // 出错
//printf("%s\n", str); // 出错
printf("%d\n", sizeof(str)); // 4
printf("%d\n", strlen(str)); // 5
printf("%d\n", sizeof("hello world")); // 12
printf("%d\n", strlen("hello world")); // 11
return 0;
}
/*
字符指针数组: 每一个元素都是字符指针的数组
*/
int main()
{
char str1[128] = "hello";
char str2[128] = "world";
char str3[128] = "heihei";
char *a[3] = {str1, str2, str3};
for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
printf("%s\n", a[i]);
}
char **p = a;
for (int i = 0; i < 3; i++)
{
// p+i 得到的是 a[0] a[1] a[2] 地址
//*(p+i) 得到的是 a[0] a[1] a[2] 的值, 保存的是 str1, str2, str3 首元素地址
//printf("%s\n", *(p + i));
printf("%s\n", p[i]);
}
printf("%c\n", *(*(p + 1) + 2)); // r
p[1][2] = 'w';
printf("%c\n", p[1][2]); // w
printf("%c\n", p[2][3]); // h
return 0;
}
4.10 练习
4.10.1 两头堵模型
/*
将前后均含有空格的字符串称为两头堵,
要求:
去掉字符串 " abc def ", 两头的空格, 中间的空格保留,
即最后结果为: "abc def"
*/
char *delete_space (char *str)
{
char *start = str;
char *end = str + strlen(str) + 1;
while(*start == ' ')
start++;
while(*end == ''
end--);
*(end + 1) = 0;
return start;
}
4.10.2 在一个字符串中查找另一个字符串出现次数
int find_str_cnt (char *src, char *dst)
{
int cnt = 0;
while(*src)
{
if(*src == *dst)
{
if(strncmp(src, dst, strlen(dst)) == 0)
{
cnt++;
src += strlen(dst) - 1;
}
}
src++;
}
return cnt;
}
4.10.3 打字游戏
/*
生成 26 个随机字母, 在终端输入 26 个字母和随机生成的比较,
相同则显示出来, 不相同则显示下划线,
字母输入完毕后将正确率和输入耗时打印出来
*/
/*
1. 字符输入函数(无需回车)
getch(); // Windows, 头文件 #include <conio.h>
getch(); // Linux, 头文件 #include <surses.h>
2. 随机数生成
#include <stdlib.h> // srand(), rand()
#include <time.h> // time()
srand((unsigned int)time(NULL)); // 随机种子
rand(); // 随机数
3. 耗时
time(NULL); // 获得当前系统的时间
*/
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#define SIZE 51
int main()
{
srand(time(NULL));
char rand_char[SIZE] = "";
int count = 0;
int start = 0;
int end = 0;
for (int i = 0; i < SIZE-1; i++)
rand_char[i] = rand() % 26 + 97;
for (int i = 0; i < SIZE - 1; i++)
{
char ch = getch();
if (ch == rand_char[i])
{
printf("%c", ch);
count++;
}
else
printf("_");
if (i == 0)
start = time(NULL);
if (i == SIZE - 2)
end = time(NULL);
}
printf("\n");
printf("Accuracy: %%%.0lf\n", 100 * (double)count / (SIZE - 1));
printf("Time: %d\n", end - start);
return 0;
}
4.10.4 将数组的 \0 后置
/*
char str[128] = "n\0ahello\0world\0bh\0eiha\0\0\0\0";
---->str[128] = "nahelloworldbheiha\0\0\0\0\0\0\0\0";
*/
#include <stdio.h>
int main()
{
char str[128] = "n\0ahello\0worl\0\0\0d\0bh\0\0ha\0\0\0\0\0\0\0\0\0\0";
char *start = str;
char *end = str + sizeof(str) - 1;
char *next = NULL;
while ( start != end+1)
{
if (*start == 0)
{
next = start + 1;
while (*next == 0 && next != end + 1)
next++;
if (next == end + 1)
break;
char tmp = *start;
*start = *next;
*next = tmp;
}
start++;
}
printf("%s\n", str);
return 0;
}
4.10.5 指针的自加运算
#include <stdio.h>
int main()
{
int a[] = {1, 2, -1, 2, 3, 4, 5, 6, 7, 8, 9, 34, 23};
char *p = &a[3];
//int *p = &a[3];
printf("%d\n", *p++); // 2
printf("%d\n", *(p++)); // 0
printf("%d\n", (*p)++); // 0
printf("%d\n", ++(*p)); // 2
printf("%d\n", *++p); // 0
printf("%d\n", *(++p)); // 3
//另外一种答案: 0 0 0 2 2 0
return 0;
}
/*
解题思路: p 是 char * 类型, 与 a 类型不匹配, 所以 p 会转成 int *,
个人计算机多为小端模式, 所以 p 可以看成是一个保存{2, 0, 0, 0, 3, 0...}的数组
*p++, ++ 在后, 先取值, 再自加, 先打印 *p, 再 p++;
*(p++), ++ 在后, 先打印 *p, 再 p++;
(*p)++, ++ 在后, 先打印 *p, 再值自加, 即此时变成{2, 0, 1, 0, 3, 0...};
++(*p), 先打印 *p, 再值自加, 即此时变成{2, 0, 2, 0, 3, 0...};
*++p, 先 p++, 再取值;
*(++p), 先 p++, 再取值。
*/
4.11 typedef
typedef unsigned long size_l;
typedef int array[10]; // int [10] 取别名
typedef int (*arr)[10]; // int (*)[10] 取别名
int main()
{
size_l num; // num 是 unsigned long
int a[8] = {1, 2, 3, 4, 5, 6, 7}
array *p = &a;
arr q = &a;
return 0;
}
4.12 函数指针
4.12.1 函数指针定义
/*
函数指针: 存储一个函数地址的指针
*/
void fun (int a)
{
printf("%p\n", &a);
}
int main()
{
//函数的地址就是函数名
fun(4);
printf("%p\n",fun);
return 0;
}
/*
函数指针的定义
*/
int main()
{
typedef void FUN(int a); // 给void (int a)函数类型取别名为FUN
FUN *p = fun;
p(4);
typedef void (*MYFUN)(int a); // 给 void (*)(int a) 函数指针类型取别名为 MYFUN
MYFUN q = fun; // q 本身就是一个指针变量
q(10);
return 0;
}
/*
对数组指针取 * 取的是该一维数组的首元素地址
对函数指针取 * 取的还是函数的地址
*/
int main()
{
//void fun(int a); *p; void (*p)(int a);
void(*p)(int a) = fun;
//p 是一个指针变量, 类型是 void(*)(int a) 函数指针保存的是返回值为 void, 参数为 int 类型的函数地址
p(10);
//(&p)(30); p是一个变量
(*p)(20); // 对函数指针取 * 编译器认为还是函数的地址
(&fun)(10); // 对函数名取 & 编译器认为还是函数的地址
(*fun)(10); // 对函数名取 * 编译器认为还是函数的地址
(******fun)(10); // 对函数名取 * 编译器认为还是函数的地址
//void *ptr = 0xabcd11;
//int (*p)(int, int)
//((int(*)(int, int))0xabcd11)(3, 5);
return 0;
}
4.12.2 函数指针数组
/*
函数指针数组: 每一个元素都是函数指针的数组
*/
int myadd(int a, int b)
{
return a + b;
}
int mysub(int a, int b)
{
return a - b;
}
int mymul(int a, int b)
{
return a * b;
}
int mydive(int a, int b)
{
return a / b;
}
int main()
{
/*
int (int a, int b) 函数类型
函数指针数组 int(*p[4])(int, int) 每个元素函数指针
整形指针数组int* a[4] 数组的每个元素是整形指针 int*
字符指针数组 char *a[4] 组的每个元素是 char*
在定义时符号与 [] 结合这个是数组 与 () 结合是函数
*/
int(*p[4])(int, int) = {myadd, mysub, mymul, mydive};
printf("%d\n", p[0](10, 20));
typedef int FOO(int a, int b);
FOO * q[4] = {myadd, mysub, mymul, mydive};
printf("%d\n", q[1](10, 20));
typedef int (*_FOO)(int a, int b);
_FOO k[4]= {myadd , mysub, mymul, mydive};
_FOO (*j)[4] = &k; // 函数指针数组指针
printf("%d\n", k[2](10, 20));
return 0;
}
/*
函数指针的应用
*/
int main()
{
char *s[] = {"myadd", "mysub", "mymul", "mydive", "mymod", "mymax"};
typedef int FOO(int a, int b);
FOO * p[] = {myadd, mysub, mymul, mydive, mymod, mymax};
char cmd[128] = "";
int a = 0;
int b = 0;
while (1)
{
scanf("%s %d %d", cmd, &a, &b);
for (int i = 0; i < sizeof(s) / sizeof(s[0]); i++)
{
if (strcmp(cmd, s[i]) == 0)
{
printf("%d\n",p[i](a, b));
break;
}
}
}
}
4.12.3 回调函数
/*
简单来说, 回调函数就是在别人的程序或工程中使用自己定义的函数,
C 语言的回调函数只能由函数指针实现
*/
/*
mymath.c
*/
int myadd(int a, int b)
{
return a + b;
}
int mysub(int a, int b)
{
return a - b;
}
int mymul(int a, int b)
{
return a * b;
}
int mydiv(int a, int b)
{
return a / b;
}
int mymod(int a, int b)
{
return a % b;
}
int mymax(int a, int b)
{
return a > b ? a : b;
}
/*
mymath.h
*/
#pragma once
int myadd (int a, int b);
int mysub (int a, int b);
int mymul (int a, int b);
int mydiv (int a, int b);
int mymod (int a, int b);
int mymax (int a, int b);
/*
main.c
*/
#include <stdio.h>
#include "mymath.h"
//typedef int MATH(int, int);
void test (int (*p)(int, int), int a, int b)
{
printf("%d\n", p(a, b));
}
int main()
{
test(myadd, 10, 20);
test(mysub, 10, 20);
test(mymul, 10, 20);
return 0;
}
标签:return,语言,int,char,++,printf,第四章,指针 来源: https://blog.csdn.net/qq_46042223/article/details/121003226
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。