知识特点(学习方向)
上一篇介绍了更深一步的指针,这一篇将涉及 字符指针变量,数组指针变量,⼆维数组传参的本质,函数指针变量,函数指针数组,转移表,assert断言等一些知识点,每篇内容不多很好理解,相信大家看完这几篇之后会对指针的理解有质的飞跃
深入理解未来会继续连更3篇,如果需要可以先关注。
每一个知识点都讲的很细节,已经学明白并掌握第一节指针基础知识点合集1,2(侧边有合集栏)的同学让我们一起继续深一步的学习。
多运用类比,希望大家能和前面知识点联系起来,多比较,多研究,祝大家学习愉快。
1.字符指针变量(char*)
指针的类型中我们知道有⼀种指针类型 为 字符指针 char*
一般使用:
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
赋值字符串的形式(思考:是将一个字符串放到pstr指针里了吗)
#include<stdio.h>
int main()
{
const char* pstr = "hello xst";
printf("%s\n", pstr);
return 0;
}
答案:本质是把字符串 hello xst ⾸字符的地址放到了pstr中
上⾯代码的意思是把⼀个常量字符串的⾸字符 h的地址存放到指针变量 pstr 中
(首地址,首地址,首地址)
1.1学习《剑指offer》中相关笔试题
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char *str3 = "hello bit.";
const char *str4 = "hello bit.";
if(str1 ==str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if(str3 ==str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
//会打印什么??
这⾥str3和str4指向的是⼀个同⼀个常量字符串。C/C++会把常量字符串存储到单独的⼀个内存区域, 当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。
但是⽤相同的常量字符串去初始 化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同
2.指针数组&数组指针变量(通过类比通俗易懂一次掌握)
2.1指针数组(指针基础知识点合集2已经讲解)
整型数组:存放整形
字符数组:存放字符
指针数组:存放指针(数组中的每个元素都是用来存放地址(指针)的)
2.2数组指针变量(依然需要类比)
整形指针变量:指向整形数据的指针,即存放的是整形变量的地址
字符指针变量:指向字符数据的指针,即存放的是字符变量的地址
数组指针变量:指向数组数据的指针,即存放的是数组数据的地址
2.3两者代码比较
分析两段代码各代表什么意思
1.int *p1[10];
2.int (*p2)[10];
1.首先int和*会先结合,(学会分段:切开一部分一部分看)
int* p1[10]
划分开再看:p1[10]就是我们最开始学的数组,那int*就是这个数组中元素的类型,也就是每个元素都是整形指针,即这是一个存放整指针的数组,是整形指针数组
2.(*p2)+括号了,因此说明p是一个指针变量,然后指着指向的是⼀个⼤⼩为10个整型的数组,
因此这是一个数组指针变量
3.数组指针变量怎么初始化
数组指针变量是⽤来存放数组地址的,如果要存放个数组的地址,就得存放在数组指针变量中,如下:
int(*p)[10] = &arr;
4.⼆维数组传参的本质
⼆维数组传参本质上也是传递了地址,传递的是第⼀ ⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:
#include <stdio.h>
void test(int (*p)[5], int r, int c)
{
int i = 0;
int j = 0;
for(i=0; i<r; i++)
{
for(j=0; j<c; j++)
{
printf("%d ", *(*(p+i)+j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};
test(arr, 3, 5);
return 0;
}
5.函数指针变量
函数指针变量是⽤来存放函数地址的,未来通过地址能够调⽤函数的。(依然类比)
例如:
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
int main()
{
int(*pf3)(int, int) = Add;
printf("%d\n", (*pf3)(2, 3));
printf("%d\n", pf3(3, 5));
return 0;
}
6.typedef关键字
重命名(昨天晚上发的顺序表就用了),可以将复杂的类型,简单化。
例如:
typedef unsigned int uint;
//将unsigned int 重命名为uint
7.函数指针数组
那要把函数的地址存到⼀个数组中,那这个数组就叫函数指针数组
int (*parr1[3])();
8.转移表(函数指针数组的⽤途)
举例:计算器的⼀般实现:
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
使⽤函数指针数组的实现:
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf( "请选择:" );
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf( "输⼊操作数:" );
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y);
printf( "ret = %d\n", ret);
}
else if(input == 0)
{
printf("退出计算器\n");
}
else
{
printf( "输⼊有误\n" );
}
}while (input);
return 0;
}
9.assert断言
assert.h 头⽂件定义了宏 assert() ,在运⾏时确保程序符合指定条件,如果不符合,就报 错终⽌运⾏。这个宏被称为“断⾔”
assert(p != NULL);
assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零), assert() 不会产⽣ 任何作⽤,程序继续运⾏。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误 流 stderr 中写⼊⼀条错误信息,显⽰没有通过的表达式,以及包含这个表达式的⽂件名和⾏号
使⽤ assert() 有⼏个好处:它不仅能⾃动标识⽂件和 出问题的⾏号,还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问 题,不需要再做断⾔,就在 #include 语句的前⾯,定义⼀个宏 NDEBUG
#define NDEBUG
#include <assert.h>
重新编译程序,编译器就会禁⽤⽂件中所有的 assert() 语句。如果程序⼜出现问题,可以移 除这条 #define NDEBUG 指令(或者把它注释掉),再次编译,这样就重新启⽤了 assert() 语 句。
assert() 的缺点是,因为引⼊了额外的检查,增加了程序的运⾏时间。 ⼀般我们可以在 Debug 中使⽤,在 Release 版本中选择禁⽤ assert 就⾏,在 VS 这样的集成开 发环境中,在 Release 版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题, 在 Release 版本不影响⽤⼾使⽤时程序的效率
完—
今天的分享就先到这里,每篇涉及的知识点不是很多但都十分相信,希望对大家有所帮助,有什么问题或者指教都可以提出来哈,记得点赞关注哦!
小预告(明日老时间分享):
1.回调函数是什么?
2. qsort使⽤举例
3. qsort函数的模拟实现
等内容
祝大家工作顺利,学业有成!!!