[C]关键知识点的理解

C中关键知识点的理解

NULL

NULL不是C语言的关键字,本质是一个宏定义,在C/C++中NULL的标准定义:

#ifdef _cplusplus        //条件编译
#define NULL 0
#else
#define NULL (void *)0    //对应C语言
#endif

编译器会根据宏_cplusplus值来判断当前的编译环境,在C中,NULL本质是0,但不是当做一个数字解析,而是当一个内存地址来解析的,这个0表示0x00000000,代表内存的0地址。(void *)0 这个整体表示一个指针。对于定义的指针变量时,一般使用NULL来初始化指针变量,这是不会出现野指针问题,而大部分CPU中, 内存的0地址处都不是可以随便访问的(一般为操作系统严密管控区域,应用程序不能随便访问)所以野指针指向了该区域可以保证野指针不会造成误伤。若程序无意识的解引用,指向0地址处的野指针则会触发段错误,也可提示你找到出程序中的错误。

'\0'、'0'、0 和NULL的区别

  1. '\0'是一个转义字符,它对应的ASCII编码值是0,本质就是0;
  2. '0'是一个字符,它对应的ASCII的编码值是48,本质是48;
  3. 0是一个数字,它就是0,本质就是0;
  4. NULL是一个表达式,是强制类型转换为void*类型的0,本质是0;

实际应用中,'\0'是C语言字符串的结尾标志,一般用来比较字符串中的字符以判断字符串有没有到头

'0'是字符0,对应0这个字符的ASCII编码,一般用来获取0的ASCII码值

0是数字,一般用来比较一个int类型的数字是否等于0

NULL是一个表达式,一般用来比较指针是否是一个空指针

强制类型转换

C语言运算中的临时匿名变量,就是C在强制类型转换是产生的一个临时匿名变量。

#include<stdio.h>

int main(int argc,char *argv[]){
  float a = 12.34;
  int b = (int)a;                //(1)
  printf("a = %f,b = %d.\n",a,b);
  return 0;
}

运行结果

a = 12.340000, b =12

在代码(1)中,将 浮点类型的a强制类型转换成int类型,a本身并没有发生改变,(int)a强制类型并赋值在底层实际分了四个步骤:

  1. 在另外的地方找一个内存构建一个临时变量x(x的类型是int,x的值等于a的整数部分);
  2. 将float a的值的整数部分赋值给x;
  3. 将x赋值给b;
  4. 销毁x;

最后结果:a还是float而且值保持不变,b是a的整数部分;

为什么必须先定义或声明才能调用函数/变量?

C语言编译器在编译过程安照顺序结构,一个C语言程序中有多个.c文件,编译时多个.c文件是独立分开编译的。每个.c文件编译时,编译器是按照从前往后的顺序逐行进行编译的;编译器的编译时的顺序编译会导致函数/变量必须先定义或声明才能调用,这也是C语言中函数/变量声明的来源。

为什么本质都是顺序结构?

顺序结构本质上符合CPU的设计原理,CPU就是以顺序方式去执行每条指令的,CPU是人设计的,所以CPU的设计符合人的思维原理。

简单描述一下操作系统的作用

实际上操作系统(OS)是一个软件,该软件用于实现计算机硬件管理,达到高效利用计算机资源、高效开发大型项目和高效升级软件的目的。OS处于上层应用和下层硬件之间的中间层。OS对下管理硬件,对上向应用提供服务接口。分别是CPU管理、内存管理、任务管理、问价管理和I/O设备管理

简略描述强制类型转换会导致变量的那些方面的改变

首先理解数据类型的作用是什么,数据类型最重要的作用是决定了遍历空间字节数和解析方式。所以想对应的,数据的强制类型改变是实际需要的字节数和解析方式,因此有可能会导致数据的丢失,因此在进行强制数据类型转换操作时,一定要格外的小心。

指针数组和数组指针

什么是指针数组?从文字角度来理解,一般放在前面的事修饰语,放在后面是主语。指针数组的实质是一个数组,且该数组里面的元素全部是指针变量,故叫指针数组。而数组指针的实质是一个指针,因为该指针指向的是一个数组,故称为数组指针

C语言编译器是怎么区分指针和数组呢?指针在于这个星号*。数组就在于p后面有中括号[]。要搞清楚你定义的符号是谁第一步:找核心,第二步:找结合(注意符号优先级)。如int *p;核心是p,p跟谁结合?两个选择,一个是星号*,一个是分号,根据一般规律,分号不结合的,因此p与星号结合成*p,左边是int,右边是分号,因为分号不结合,因此*p与int结合表示p这个指针指向int类型的数据。又如int p[5];中,核心是p,p左边是int,右边是[],根据优先级,p与括号[]结合成数组p[]左边是int,右边是分号,因为分号不结合,所以p[]与int结合表示数组中的元素是int型的

注意:若核心和星号*结合,表示核心是指针;若核心和中括号[]结合,表示核心是数组;如果核心和小括号结合,表示核心是函数。遇到优先级问题,第一查优先级表,第二,先记住中括号[]等几个优先级比较高的符号即可

#include<stdio.h>
int main(int argc,char *argv[]){
  int *p = NULL;
  int a[4];
  p = a;
  return 0;
}

p是一个int类型的指针,让它指向一个数组名(数组名做右值表示数组首元素的首地址),左右类型匹配,编译器不会告警。 下面代码,两类型不匹配,GCC编译时会出现告警:

#include<stdio.h>
int main(int argc,char *argv[]){
  int *p = NULL;
  int a[4];
  p = &a;    //两类型不匹配,GCC编译时会出现告警
  return 0;
}

改进代码

#include<stdio.h>
int main(int argc,char *argv[]){
  int a[4];
  int (*p1)[4];     //数组指针
  p1 = &a;
  return 0;
}

p1是int(*)[]类型,而&a也是int(*)[]类型,编译时不会告警。

数组指针与指针数组的区别

数组指针(也称行指针)

定义:int (*p)[n];  ()优先级高,首先说明p是一个指针指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长,即说执行。p+1时,p要跨过n个整型数据的长度。将二维数组赋给一指针:

int a[3][4];
int (*p)[4];	//该语句定义一个数组指针,指向含4个元素的一维数组
p = a;			//将该二维数组的首地址赋给p,即a[0]或 &a[0][0]
p++;			//该语句执行过后,即p = p+1;p跨过行a[0][]指向了行a[1][]

 所以数组指针成称为指向一位数组的指针,即行指针

指针数组

指针数组指每个数组元素为指针变量;定义 int *p[n]; [ ]优先级高,先与p结合成为一个数组,再由int *说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1是错误的,这样赋值也是错误的:p = a;

因为p是个不可知的表示只存在p[0]、p[1]、p[2]、...、p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样*p = a;这里*p表示指针数组第一个元素的值,a的首地址的值。如果要将二维数组赋给一指针数组:

#include<stdio.h>
int main(int argc,char *argv[]){
  int *p[3];	//表示一个一维数组内存放三个指针变量,分别是p[0]、p[1]、p[2] 
  int a[3][4];
  int i;
  for(i=0;i<3;i++){
  	p[i] = a[i];	//分别赋值
	printf("%d.\n",p[i]); 
  } 
  return 0;
}

运行结果

6487472.
6487488.
6487504.

两者的区别数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间指针数组是多个指针变量以数组形式存在内存中,占有多个指针的存储空间。另外,用来指向二维数组时,其引用和用数组名引用都是一样的。如要表示数组中i行j列一个元素:*(p[i]+j)、*(*(p+i)+j)、、(*(p+i))[j]、p[i][j]。

二维数组的内存是内存管理分配的,我们不需要知道怎么分配的内存,但需要知道分配之后内存分布规律,便于更深刻理解的二维数组。

优先级:()> []> *

函数指针

函数指针是什么?函数指针的实质是指针。本身占4个字节(32位系统中,所有指针都是4个指针)。字符指针在32位操作系统中占4个字节,在64位操作系统占多少字节?8个字节(在64位系统中,所有指针都占8个字节)。

函数指针、数组指针、普通指针之间没有本质区别,去呗在于指针指向的东西是什么?

函数的实质是一段代码,这一段代码在内存中是连续分布的(一个函数的大括号括起来的所有语句将来编译出来生成的可执行程序是连续的),所以对于函数来说,很关键的事函数中的第一句代码的地址。该地址即为函数地址,在C中,用函数名这个符号来表示。函数名的实质是函数这段代码的首地址

函数指针就是一个普通变量,这个变量类型就是函数指针变量类型,它的值就是某个函数的地址。(即它的函数名这个符号在编译器中对应的值)

若设函数为void func(void),函数的传参是void类型,函数的返回值也是void类型。该函数对应的函数指针为void (*p)(void);

p为函数名,类型是void(*)(void)。下面用代码来测试:

#include<stdio.h>
void func(void){
  printf("I am func1.\n");
}
int main(void){
  void (*pFunc)(void);
  pFunc = func1;        //左边是一个函数指针变量,右边是一个函数名    
  pFunc();
  return 0;
}

#include <filename.h>和#include "filename.h"区别

  • 对于#include <filename.h>,编译器从工程文件指定路径搜索filename.h;
  • 对于#include "filename.h",编译器从当前路径和工程指定路径搜索filename.h

sizeof与strlen理解

char str[]="hello";
sizeof(str)= 6 //?
sizeof(str[0])= 1 //?
strlen(str) = 5 //?

char *p = str;
sizeof(p) = 6//?错误 ,这个应该为指针变量本身的字节长度:8
sizeof(*p) = 1;// ?
strlen(p) = 5; //?

结果

sizeof(str) 6.
sizeof(str[0]) 1.
strlen(str) 5.
sizeof(p1) 8.
sizeof(*p1) 1.
strlen(p1) 5.

const作用

const int a = 4;    //定义常量a,其值一直为4
const int *p;       //指针变量p可变,而p指向的变量不可变
int const *p;       //指针变量p可变,而p指向的变量不可变

int *const p;       //指针变量p不可变,而p指向的变量可变

const int *const p;       //指针变量p不可变,而p指向的变量不可变

const修饰形参的作用

void fun(int const *p){}

形参:指针变量的指向可以变,指向的内容不可以被修改

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值