当前位置:网站首页 > Java基础 > 正文

java的基础c语言



C语言的概述

C语言被称呼为:“C生万物,编程之本”

与C语言相关的语言很多。

其中最早的一门语言叫 Algol 60,是 1960 年产生的,它是真正的第一门面向问题的语言。

1963 年剑桥大学在 Algol 60 的基础上研发出了 CPL。

1967 年剑桥大学的Martin Richards对 CPL 进行了简化,产生了 BCPL。

1970 年,美国 AT&T公司【美国电话电报公司(American Telephone and Telegraph Company)】所属贝尔实验室(AT&T Bell Laboratory)的研究员Ken Thompson以 BCPL 为基础,设计出了很简单而且很接近硬件的B语言(取 BCPL 的首字母)。

1971 年,贝尔实验室的Dennis Ritchie加入了Ken Thompson的开发项目,合作开发 UNIX。

UNIX 系统是世界上第一个真正的操作系统。

1972 年,Dennis Ritchie在B语言的基础上最终设计出了一种新的语言,C语言

1973 年年初,C语言的主体完成。Ken Thompson和Dennis Ritchie开始用C语言完全重写 UNIX,这就是 UNIX 第 5 版。

由于 UNIX 操作系统是用C语言编写的,而这个系统很流行,于是C语言也跟着流行起来。

随后又出现了 C++。

   C++ 是Bjarne Stroustrup编写的,  他也来自贝尔实验室,是C语言创始人Dennis Ritchie的下属。

后来 Sun 公司又对 C++ 进行改写,产生了 Java。

而微软公司发现 Java 很流行,就造出了一个类似的语言——C#

要学习 C++、Java 或者 C# 的话,那么C语言就最好要学一下!

Win上搭建C语言的学习环境

MinGW-W64下载网页:https://www.mingw-w64.org/

下载的两个选择,压缩版和在线exe安装版

下面对几个选项给出说明

x86_64:表示MinGW-W64要在64为的操作系统上运行

i686:表示MinGW-W64要在32位的操作系统上运行

posix:表示MinGW-W64在非windows系统上运行

win32:表示MinGW-W64在windows系统上运行

sjlj:表示MinGW-W64编译生成的程序可以在32位和64位系统上运行

dwarf:表示MinGW-W64编译生成的程序只能在32位系统上运行

seh:表示MinGW-W64编译生成的程序只能在64位系统上运行

下载压缩包的话,选择合适位置解压,将mingw64/bin加入环境变量即可

MinGW系列只提供了名字为 mingw32-make.exe 的执行文件,

事实上,该.exe 和make.exe 功能一样,为了make执行时能找到该文件,

image.png

image.png

验证是否成功配置
image.png

记事本上写第一个C语言的程序:

hello.c

 

     用编译器gcc,从一个C语言的源文件到执行,要经历的过程:

1、C源文件

2、预处理 gcc -E hello.c > hello.i , 凡是'#'开头的内容都是在预处理阶段进行处理,hello.i就是预处理后的结果文件。

3、编译 gcc -S hello.i , 默认会生成编译的结果文件hello.s , 做的事情其实就是生成汇编语言。

4、汇编 gcc -c hello.s , 默认会生成汇编语言的汇编成目标文件hello.o , 这个时候hello.o就已经是一个二进制文件。

5、链接 gcc hello.o -o hello ,就直接生成可执行文件了。

6、可执行文件 hello 就可以执行当前目标下的hello了。

image.png

image.png

    上面这样做很麻烦,只是为了让大家了解gcc编程c语言内部要经历的过程!
直接gcc hello.c 或者 gcc hello.c -o hello 或者 gcc -g hello.c -o hello 就可以了,

gcc会自动完成上面我们讲的所有步骤!
加上-g 选项,会保留代码的文字信息,便于后面的调试。

image.png

image.png

编辑工具

下载安装vscode:
https://visualstudio.microsoft.com/zh-hans/
设置中文:
  命令面板【Ctrl+Shift+P】--config 然后搜索出来的【Configure Display Language】
输入Chinese,然后选择【Chinese (Simplified)Language Pack for Visual Studio Code】,然后点击右侧的【Install】
安装好中文语言包之后软件会提示重启VSCode,点击【Yes】重启VSCode软件
安装插件

  • Code Runner:右键即可编译运行单文件,很方便;但无法Dubug
  • C/C++:又名 cpptools,提供Debug和Format功能
    image.png

    image.png

载入写好的hello.c文件
  打开vscode载入刚刚的c文件,网上网友说编译c文件要打开文件所在的文件夹,我尝试了下,确实是这样的。如下图:

image.png

然后点运行--启动调试,他会跳出一个launch.json的一个配置文件:是要修改的
image.png

image.png

 

program,指明了需要运行的文件,${}的格式是表明这是变量。

miDebuggerPath,是我们安装gdb的路径,gdb工具是用来调试的二进制文件(可执行二进制文件也叫命令)。

preLauchTask,是一个重点,我们这个配置的意思是用gdb去调试 program参数指定的二进制文件,
java的基础c语言  但如果没有这个文件怎么办,我们可以运行gcc编译工具生成二进制*.exe文件,preLaunchTask就是干这个的!

image.png

image.png

tasks.json,下面是一个任务时的配置,多个任务用[]括起来

 

用CMD
命令面板(Ctrl+Shift+P)中,输入select选择第一条:

之后选择目标Shell

Linux+Win10上搭建C语言的学习环境

1)在VMware上安装Ubuntu20.04操作系统
下载
    可以到百度搜索ubuntu 再进入官网或,找到一个合适的下载,最新版本是22.x版,安装时选桌面安装,这样好调试c语言开发。

image.png

 

下图是安装vm-tools,安装它的好处是,可以复制贴贴到虚拟机以外的系统中

image.png

SecureCRT连接Ubuntu

首先:查询ip地址ifconfig,如果没有此命令,则要安装net-tools:sudo apt install net-tools

下面是换源,先备份本地的源配置

 

安装C语言编译器,首先进和root用户中

 

2)建立Windows和Ubuntu20.04的共享文件夹或者搭建samba服务
一,设置两系统的共享文件夹(我是桌面的workspacec).

image.png

进入共享文件夹,编译执行一个c文件测试一下效果

 

Centos7.x中进入共享文件夹,比unbuntu要麻烦一些,所以很多人用ubuntu来做共享测试

 

二,安装samba服务器
ubuntn上安装

 

centos 7上安装极samba服务器

 

win连接远程samba服务器,到目前为止,云服务器没有成功过

image.png

image.png

或按win键+R键,再输入如下图所示
image.png

image.png

若点击share文件夹,则第一次会弹出对话框,输入账号:zhonghao,密码: xiong,若进不了,则有可能是防炎啬打了,centos7上关闭防火墙 systemctl stop firewalld,Ubuntu上关闭防火墙

 

进制、数据类型、常量、变量、运算符、表达式、语句和程序

1、进制
  计算机底层保存和处理的数据都是二进制数
数据在计算机中的表示,最终以二进制的形式存在 , 就是各种 <黑客帝国>电影中那些 0… 的数字 ;
如果我们直接操作二进制的话 , 面对这么长的数进行思考或操作,没有人会喜欢。
用16进制或8进制可以解决这个问题。因为,进制越大,数的表达长度也就越短。
之所以使用16或8进制,而不其它的,诸如9或20进制,
是因为8、16,分别是2的3次方、4次方,便于和二进制转换 。

10进制
  我们最熟悉的10进制 , 用 0~9 的数表示 , 逢10进1
16进制
  如果是 16 进制 , 它就是 由 0-9,A-F组成, 与10进制的对应关系是:0-9 对应 0-9;A-F对应10-15,字母不区分大小写,逢16进1。
2进制 和 8进制
   2进制 由 0-1组成,逢2进1。
   8进制 由 0-7组成 ,逢8进1。

进制转换1.jpeg

二进制与十进制之间的转换
十进制转二进制
方法为:十进制数除2取余法,即十进制数除2,余数为权位上的数,得到的商值继续除2,依此步骤继续向下运算直到商为0为止。
(具体用法如下图)

进制转换2.jpeg

二进制转十进制

方法为:把二进制数按权展开、相加即得十进制数。


(具体用法如下图)


进制转换3.jpeg

二进制与十六进制之间的转换
二进制转十六进制
方法为:十六进制是取四合一。注意事项,4位二进制转成十六进制是从右到左开始转换,不足时补0。
(具体用法如下图)

进制转换4.jpeg

注意:java中的十六进制的数表示方法为:0x或0X的开头,比如0x12C67
十六进制转二进制

方法为:十六进制数通过除2取余法,得到二进制数,对每个十六进制为4个二进制,不足时在最左边补零。


(具体用法如下图)


进制转换4.jpeg

补码:

在计算机底层数据是使用补码的形式来存放和运算的。


一个正数的补码和它的原码的形式是相同的。

负数的补码形式:除第一位为1外,其他各位取反加1

所以补码的设计目的是:
⑴使符号位能与有效值部分一起参加运算,从而简化运算规则.
⑵使减法运算转换为加法运算,进一步简化计算机中运算器的线路设计,所有这些转换都是在计算机的最底层进行运算的时候使用,而在我们使用的汇编、C、java等其他高级语言中使用的都是原码。
补码在计算机底层中的运算例子:
负数的补码就是除符号位外,取反加1,而正数不变,正数的原码反码补码是一样的.
下面是补码的运算:
( 1 )- ( 1 )= ( 1 )+ ( -1 )
=(00000001)补+ ()补
= (00000000)补= ( 0 )正确
( 1 )- ( 2)= ( 1 )+ ( -2 )
= (00000001)补+ ()补
= ()补= ( -1 ) 正确

(-1) = ()原码=( )反码 =(( )+ 1)补码

C语言中数据类型
  在这里,我们主要任务是了解基本类型,后面的构造类型、指针有专门的章节具体讲解:

clipboard.png

short、int、long、char、float、double ...关键字代表C 语言里的基本数据类型,数据类型可以看作是“模子”


什么是“模子”? 举个例子:


大家应该见过蜂窝煤:


做蜂窝煤的这个东西叫藕煤器,拿着它在和好的煤堆里这么一咔,一个蜂窝煤出来了。


一般的半径12cm,12 个孔。不同型号的藕煤器咔出来的蜂窝煤大小不一样,孔数也不一样。


这个藕煤器其实就是个模子。

    现在我们联想一下,short、int、long、char、float、double 这六个东东其实就很像不同类型的藕煤器!
拿着它们在内存上咔咔咔,咔出来的不是蜂窝煤,而是不同大小的内存空间,
这些内存空间如果需要反复的在程序中获取来用,可以给它关联一个名字。
这个名字就是我们后面要介绍的常量或变量的名字。
    内存空间或者磁盘空间里,都是存放数据用的,描述在这些设备里的数据的存储单位:
位bit:是计算机中最小的储存单位
字节byte:一个byte是由8个bit组成,它是最小的可寻址单元,B也表示字节,它是byte的简写
1KB (Kilobyte 千字节)=1024B;
1MB (Megabyte 兆字节 简称“兆”)=1024KB;
1GB (Gigabyte 吉字节 又称“千兆”)=1024MB;
1TB (Trillionbyte 万亿字节 太字节)=1024GB;
1PB(Petabyte 千万亿字节 拍字节)=1024TB;
1EB(Exabyte,百亿亿字节,艾字节)=1024PB;
1ZB(Zettabyte,十万亿亿字节,泽字节)= 1024EB;
1YB(Yottabyte,一亿亿亿字节,尧字节)= 1024ZB;
1BB(Brontobyte,一千亿亿亿字节)= 1024YB;
注意:不同的系统平台数据类型所占空间大小可能会有所不同,具体平台可以用sizeof关键字测试一下:
一般情况下,Windows系统是:
short 咔出来的内存大小是2 个byte;
int 咔出来的内存大小是4 个byte;
long 咔出来的内存大小是4 个byte;
float 咔出来的内存大小是4 个byte;
double 咔出来的内存大小是8 个byte;
char 咔出来的内存大小是1 个byte。

 

变量和常量

clipboard (1).png

符号常量

在C语言中,可以用一个标识符来表示一个常量,称之为符号常量。


符号常量在使用之前必须先定义,其一般形式为:



其中#define是一条预处理命令(预处理命令都以"#"开头),称为宏定义命令,宏这块后面慢慢理解。


一经定义,以后在程序中所有出现该标识符的地方均代之以该常量值。


习惯上符号常量的标识符用大写字母,变量标识符用小写字母,以示区别。

 

++和移位运算符

 

其它位运算符
运算符意义示例对于每个位位置的结果(1=设定,0=清除)
<<向左移位x<<yx 的每个位向左移动 y 个位,相当于乘以2的n次方   >>向右移位x>>yx 的每个位向右移动 y 个位,相当于除以2的n次方
& 位 AND x&y 如果 x 和 y 都为 1,则得到 1;如果 x 或 y 任何一个为 0,或都为0,则得到 0
| 位 OR x|y 如果 x 或 y 为 1,或都为 1,则得到 1;如果 x 和 y 都为 0,则得到 0
^ 位 XOR x^y 如果 x 或 y 的值不同,则得到 1;如果两个值相同,则得到 0
~ 位 NOT(I的补码) ~x 如果 x 为 0,则得到 1,如果 x 是 1,则得到 0

注意:位运算符的操作数必须是整数类型。

在了解位运算符的基本规则下,要求进步掌握:
  1)将某个整数num 的p个位置的二进制位变成1:num | (1<<p);
  2)将某个整数num 的p个位置的二进制位变成0:num & ~(1<<p);
全局变量和局部变量
  前面,我们一直将变量定义在 main 函数里面,其实,变量也可以定义在 main 函数外面:

 

在函数外部定义的变量叫做全局变量(Global Variable)
在函数内部定义的变量叫做局部变量(Local Variable)
全局变量和局部变量的区别:
局部变量的作用域也仅限于函数内部,出了函数就不能使用了;
全局变量的默认作用域是整个程序,也就是所有的代码文件,包括源文件(.c文件)和头文件(.h文件)。
大体上了解C语言的内存布局
    由C语言代码(文本文件)形成可执行程序(二进制文件),需要经过预处理-编译-汇编-连接几个阶段阶段。编译过程把C语言文本文件生成汇编程序,汇编过程把汇编程序形成二进制机器代码,连接过程则将各个源文件生成的二进制机器代码文件组合成一个文件。
合成的统一文件,它由几个部分组成。
各个部分会存放在不同的内存区域中:
(1)代码段(Code或Text)
    在C语言中,程序语句进行编译后,形成机器代码。在执行程序的过程中,CPU的程序计数器指向代码段的每一条机器代码,并由处理器依次运行。
基本数学运算(+,-),逻辑运算(&&,||),位运算(&,|,^)等都属于代码段的内容。

(6)栈(stack)
    栈内存只在程序运行时出现,在函数内部使用的变量

clipboard.png

变量前的修饰符

1.const关键字

const 意思是“恒定不变的”!


用 const 定义的变量的值是不允许改变的。这也就意味着必须在定义的时候就给它赋初值。


说 const 定义的是变量,但又相当于常量;说它定义的是常量,但又有变量的属性,所以叫常变量。

 

   const 和 define比较:
      从功能上说const 和 define确实很像,但它们又有明显的不同,define是预处理命令定义的常量,而const是普通变量的定义。
 2.static关键字
   static关键字表示修饰全局变量的作用域只能是本身的编译单元(一个文件的编译)。
   static修饰的局部变量表示变量不放在栈内存里,放在读写数据段里,所以相当于本编译单元里的全局变量。
还是通过例子来理解:

 

const作用在局部变量中,相当于当前编译单元中的全局变量

 

3.volatile关键字
   volatile和编译器优化关系:
   由于内存访问速度远不及CPU处理速度,为提高机器整体性能,需要优化措施:
1)在硬件上:CPU在运算时会直接用的设备由近到远,主要有寄存器、高速缓存和内存。
    CPU与寄存器交互速度是最快的,其次是高速缓存,再次是内存。
2)执行程序的编译器常用的一个优化方法:将内存变量缓存到寄存器。
   由于访问寄存器要比访问内存单元快的多,编译器在存取变量时,为提高存取速度,编译器优化有时会先把变量读取到一个寄存器中;以后再取变量值运算时就直接从寄存器中取值。但如果这个变量的值变化很频繁,就有可能造成CPU运行的数据和变量里的实时数据不一致,造成运算结果错误,volatile的作用就是提醒编译器它后面所定义的变量随时都有可能改变,告诉编译器对该变量不做优化,要运算时都会直接从变量内存地址中读取数据,从而避免不一致的错误。

      注意:频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile,一般不用它。
4.extern关键字
   关键字extern,仅仅是声明这个变量/函数可能在别的源文件里定义。
   局部变量的声明不能有extern的修饰。在全局变量上,我们可以理解写和没写基本是一样的。
   通过例子来理解:

 

5.register关键字
register修饰符会给编译程序暗示我修饰的变量将被频繁地使用,如果可能的话,应将其保存在CPU的寄存器中,以加快其存储读取速度。

使用register修饰符有几点限制
(1)register变量必须是能被CPU所接受的类型。
这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。
不过,有些机器的寄存器也能存放浮点数。
(2)因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。
(3)只有局部变量和形式参数可以作为寄存器变量,其它(如全局变量)不行。
(4)局部静态变量不能定义为寄存器变量。不能写成:register static int a, b, c;
(5)由于寄存器的数量有限(不同的cpu寄存器数目不一),不能定义任意多个寄存器变量,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。

注意:
  早期的C编译程序不会把变量保存在寄存器中,除非你命令它这样做,这时register修饰符是C语言的一种很有价值的补充。然而,随着编译程序设计技术的进步,在决定哪些变量应该被存到寄存器中时,现在的C编译环境能比程序员做出更好的决定。实际上,许多编译程序都会忽略register修饰符,因为尽管它完全合法,但它仅仅是暗示而不是命令。
6.auto关键字
   auto被解释为一个自动存储变量的关键字,也就是申明一块临时的变量内存。
变量,没加修饰符的时候,默认就是atuo这个关键字。
注意:静态变量、全局变量申明后没初始化会有一个默认的初始值0,局部变量没有初始化值,系统随机赋值一个垃圾值,所以局部变量一定要赋值。

基本输入输出函数

   输入输出(Input and Output, IO)是用户和程序“交流”的过程。

   在控制台程序中,输出一般是指将数据(包括数字、字符等)显示在屏幕上,输入一般是指获取用户在键盘上输入的数据。
   主要讲解在stdio.h头文件申明的几个标准输入输出函数,
1--printf ,scanf:最基本输入输出函数, 是最灵活、最复杂、最常用的输入输出函数
2--getchar,putchar:针对字符的输入和输出函数,有针对性用起来简单点
3--gets,puts:针对字符串的输入输出函数,有针对性用起来简单点

1、printf函数,scanf函数

   如果在程序中要使用 printf,那么就必须要包含头文件 stdio.h,此函数包含在该头文件中的。
printf是基本的输出函数,其功能是将程序运行的结果输出到屏幕上。
printf 函数的原型为:

 

printf 的格式有三种:

 

 

   这句话的意思是将变量 i 以十进制输出。
      那么现在有一个问题:i 本身就是十进制,为什么还要将 i 以十进制输出呢?
   因为程序中虽然写的是 i=10,但是在内存中并不是将 10 这个十进制数存放进去,而是将 10 的二进制代码存放进去了。计算机只能执行二进制 0、1 代码,而 0、1 代码本身并没有什么实际的含义,它可以表示任何类型的数据。所以输出的时候要强调是以哪种进制形式输出。所以就必须要有“输出控制符”,以告诉操作系统应该怎样解读二进制数据。

 

注意:printf 中双引号内除了输出控制符和转义字符 外,所有其余的普通字符全部都原样输出。“输出控制符”和“输出参数”无论在“顺序上”还是在“个数上”一定要一一对应。
常用的输出控制符主要有以下几个:

 


一定要掌握 %x(或 %X 或 %#x 或 %#X),
因为调试的时候经常要将内存中的二进制代码全部输出,然后用十六进制显示出来。

 


printf 中有输出控制符%d,转义字符前面有反斜杠,还有双引号。那么大家有没有想过这样一个问题:
怎样将这三个符号通过 printf 输出到屏幕上呢?

 

scanf函数
  scanf是从键盘获得用户输入,和 printf 的功能正好相反。

 

image.png

注意:

对于 scanf(),输入数据的格式要和控制字符串的格式保持一致。


printf输出中文乱码是编码是问题,解决办法:终端输入:chcp 65001 回车。本人这里是系统默认的编译cmd,不是windows的cmd,所以没有出现中文乱码.

内存地址的理解:

    前面反复讲计算机里的数据是以二进制的形式保存在内存/磁盘中的,字节(Byte)是最小的可操作单位。
      为了便于管理,我们给内存中每个字节分配了一个编号,使用该字节时,只要知道编号就可以,就像每个学生都有学号,老师会让学生回答问题,喊学号就可以了。字节的编号是有顺序的,从 0 开始,接下来是 1、2、3……

-0.png

这个编号,就叫做内存的地址(Address)。


int a;会在内存中分配四个字节的空间,我们将第一个字节的地址称为变量 a 的地址,也就是&a的值。


对于前面讲到的整数、浮点数、字符,都可以使用 & 获取它们对应的变量的地址,



我们不妨将变量的地址输出看一下:

 

输入其它数据
   除了输入整数,scanf() 还可以输入单个字符、字符串、小数等,请看下面的演示:

 

最后需要注意的一点是,scanf() 读取字符串时以空格为分隔,遇到空格就认为当前字符串结束了,所以一般情况下无法读取含有空格的字符串,想要读取空格要作其它处理

2-getchar()和putchar()函数

getchar()字符输入函数,它就是scanf("%c", c)的替代品,除了更加简洁,没有其它优势了;
或者说,getchar() 就是 scanf() 的一个简化版本。
putchar(c)就是printf("%c ", c);简化版本了。

 

3-gets()和puts()函数

gets() 这个专用的字符串输入函数,puts(cc)专用打印输出字符串函数。

 

gets() 是有缓冲区的,每次按下回车键,就代表当前输入结束了,
gets() 开始从缓冲区中读取内容,这一点和 scanf() 是一样的。
gets() 和 scanf() 的主要区别是:
scanf() 读取字符串时以空格为分隔,遇到空格就认为当前字符串结束了,所以无法读取含有空格的字符串。
gets() 认为空格也是字符串的一部分,只有遇到回车键时才认为字符串输入结束。

总结:gets() 能读取含有空格的字符串,而 scanf() 不能。
scanf() 可以一次性读取多份类型相同或者不同的数据,
getchar() 和 gets() 每次只能读取一份特定类型的数据,不能一次性读取多份数据。
基本输入输出函数进阶知识点:
认识缓冲区(缓存)
   缓冲区(Buffer)又称为缓存(Cache),是内存空间的一部分。
也就是说,计算机在内存中预留了一定的存储空间,用来暂时保存输入或输出的数据,这部分预留的空间就叫做缓冲区(缓存)。

缓冲区的必要性:

clipboard.png

缓冲区的类型

根据缓冲区对应的是输入设备还是输出设备,可以分为输入缓冲区和输出缓冲区。


根据数据刷新(也可以称为清空缓冲区,就是将缓冲区中的数据“倒出”)的时机,


可以分为全缓冲、行缓冲、不带缓冲。


1) 全缓冲

在这种情况下,当缓冲区被填满以后才进行真正的输入输出操作。缓冲区的大小都有限制的,比如 1KB、4MB 等,数据量达到最大值时就清空缓冲区。

2) 行缓冲
在这种情况下,当在输入或者输出的过程中遇到换行符时,才执行真正的输入输出操作。

 

   windows操纵系统和Linux操纵系统对printf的缓冲是否默认开启有差别,win默认关闭printf的缓冲区,而其它像scanf()默认是打开的,linux默认开启,
手动开启:setvbuf(stdout, buf, _IOLBF, 512);
存在缓冲和不存在缓冲允许结果是不一样的!
下图是两种操作系统效果图

image.png

下面的代码开启了缓冲区buf,后,win和linux执行效果是一样的

 

image.png

我们可以手动刷新缓冲区的,下面代码就是用了 fflush(stdout);

 

3) 不带缓冲
不带缓冲区,数据就没有地方缓存,必须立即进行输入输出。
getche()、getch() 就不带缓冲区,输入一个字符后立即就执行了,根本不用按下回车键。
这两个方法是Windows下特有的方法,Linux和Mac上没有,就不举例了。
scanf()缓冲区问题
   首先声明一下,这个的缓冲区不管在那都是默认打开的,并且缓冲区中有 符,所以如下代码运行时是有异常的

 

image.png

处理方法:

 

scanf() 高级用法

1) 指定读取长度
在前面讲过printf() 中可以指定最小输出宽度!
就是在格式控制符的中间加上一个数字,例如,%10d表示输出的整数至少占用 10 个字符的位置:
如果整数的宽度不足 10,那么在左边以空格补齐;
如果整数的宽度超过了 10,那么以整数本身的宽度来输出,10 不再起作用。

其实,scanf() 也有类似的用法,也可以在格式控制符的中间加一个数字,用来表示读取数据的最大长度,
例如:
%2d表示最多读取两位整数;
%10s表示读取的字符串的最大长度为 10,或者说,最多读取 10 个字符。

 

就目前学到的知识而言,虽然 scanf() 可以控制字符串的长度,但是字符串中却不能包含空白符,这是硬伤,所以 scanf() 暂时还无法替代 gets()。不过后面还有scanf() 的更高级用法,届时 scanf() 就可以完全替代 gets(),并且比 gets() 更加智能。
2) 匹配特定的字符
%s 控制符有两个缺点:
%s 不能读取特定的字符,比如只想读取小写字母,或者十进制数字等,%s 就无能为力;
%s 读取到的字符串中不能包含空白符,有些情况会比较尴尬,例如,无法将多个单词存放到一个字符串中,因为单词之间就是以空格为分隔的,%s 遇到空格就读取结束了。

 

3)使用连接符
连字符左边的字符对应一个 ASCII 码,连字符右边的字符也对应一个 ASCII 码,位于这两个 ASCII 码范围以内的字符就是要读取的字符。注意,连字符左边的 ASCII 码要小于右边的,如果反过来,那么它的行为是未定义的。

常用的连字符举例:
%[A-Z]表示读取 ABC...XYZ 范围内的字符,也即大写字母;
%[0-9]表示读取 012...789 范围内的字符,也即十进制数字。
%[a-z]表示读取 abc...xyz 范围内的字符,也即小写字母;

你也可以将它们合并起来,例如:
%[a-zA-Z]表示读取大写字母和小写字母,也即所有英文字母;
%[a-zA-Z0-9]表示读取所有的英文字母和十进制数字;
%[0-9a-f]表示读取十六进制数字。

 

4)不匹配某些字符
假如现在有一种需求,就是读取换行符以外的所有字符,或者读取 0~9 以外的所有字符,该怎么实现呢?
总不能把剩下的字符都罗列出来吧,一是麻烦,二是不现实。

scanf() 允许我们在%[ ]中直接指定某些不能匹配的字符,具体方法就是在不匹配的字符前面加上^,例如:
%[^ ]表示匹配除换行符以外的所有字符,遇到换行符就停止读取;
%[^0-9]表示匹配除十进制数字以外的所有字符,遇到十进制数字就停止读取。

 

5) 丢弃读取到的字符
在前面的代码中,每个格式控制符都要对应一个变量,把读取到的数据放入对应的变量中。其实你也可以不这样做,scanf() 允许把读取到的数据直接丢弃,不往变量中存放,具体方法就是在 % 后面加一个,例如:
%
d表示读取一个整数并丢弃;
%[a-z]表示读取小写字母并丢弃;
%
[^ ]表示将换行符以外的字符全部丢弃。

 

解决问题,scanf函数能获取带空格的字符串数据,完全替换gets函数的写法:

 

流程控制

   C语言中的流程控制可以总结为三大结构:顺序结构、选择结构和循环结构
1-顺序结构就是让程序按照从头到尾的顺序依次执行每一条C语言代码,不重复执行,也不跳过任何代码。
2-选择结构也称分支结构,就是让程序有选择性的执行代码,可以跳过没用的代码,只执行有用的代码。
3-循环结构就是让程序不断地重复执行同一段代码,在操场转圈圈一样。
   顺序结构普通的程序就是依次执行每一条C语言代码,我们前面写的所有Demo都是这种结构。

选择结构

1、if else语句

 

多个if else语句

 

if语句的嵌套

 

2、switch case语句
C语言虽然没有限制 if else 能够处理的分支数量,
但当分支过多时,用 if else 处理会不太方便,代码量也稍微有点冗余。

 

?和:组成的条件运算符

 

循环结构

1、while循环
通过例子来讲while循环:
用 while 循环计算1加到100的值:

 

while循环使用注意两点:
1循环条件恒定成立时的话,while 循环会一直执行下去,永不结束,成为“死循环”。
2 循环条件不成立的话,while 循环就一次也不会执行。
2、do while循环
用do-while计算1加到100的值:

 

大体上和while循环差不多,唯一的差别:
1)while循环:先判断控制循环的逻辑条件,在执行循环体,逻辑条件为false,就不会执行循环体。
2)do while循环:不管逻辑条件是true还是false,都先执行一次循环体,然后在判断条件决定是否继续执行。
3、for循环
对比while循环,下面语句①②③都是必不可少的,被放到了不同的地方,代码结构较为松散,容易漏掉!

 

在实际开发中常常用for循环的写法代替上面讲的while循环,尽管效果没什么差别:

 

上面是for循环规规矩矩的写法,下面我们在看看不规矩的写法:

  1. 修改“从1加到100的和”的代码,省略“表达式1(初始化条件)”
 
  1. 省略了“表达式2(循环条件)”,如果不做其它处理就会成为死循环。例如:
 
  1. 省略了“表达式3”,这时可在循环体中加入修改变量的语句。例如:
 
  1. 省略了“表达式1”和“表达式3”。例如:
 

相当于:

 
  1. 3个表达式可以同时省略。例如:
    for( ; ; ) 语句
    相当于:
    while(1) 语句
    注意:尽量不要在循环体中定义条变量,这会大量浪费内存,可以先定义一个变量,再在循环中重新赋值。

4、break和continue关键字
当 break 关键字用于 while、for 循环时,会终止循环。
break 关键字通常和 if 语句一起使用,即满足条件时便跳出循环。
continue 语句的作用是跳过循环体中剩余的语句而强制进入下一次循环。
continue语句只用在 while、for 循环中,常与 if 条件语句一起使用,判断条件是否成立。

 
 

5、循环嵌套举例
例子1:简单的for循环嵌套

 
 

例子3:输出九九乘法表。

 

循环问题
1.错误:只允许在 C99 模式下使用‘for’循环初始化声明
for (int i = 0; i < (sizeof(a) / sizeof(a[0])); i++) 。。。。
处理:即把int i=0;定义在循环体外。

数组

   数组(Array)就是一些列具有相同类型的数据的集合,这些数据在内存中依次挨着存放,彼此之间没有缝隙。
C语言数组属于构造数据类型。
   为什么要在编程里设计数组这种东西:为了减少变量数目,让开发更有效率,能让多个数据绑定一个变量名。

 

有了数组后,上面的一堆代码可以这样写:

 

数组的定义方式:
dataType arrayName[length];
dataType 为数据类型,arrayName 为数组变量名称,length 为数组长度。
例如:
int a[16]; //定义一个长度为16的整数类型数组
float m[12]; //定义一个长度为 12 的浮点型数组
char ch[9]; //定义一个长度为 9 的字符型数组
注意:C语言中的数组是静态的,数组一旦被定义后,占用的内存空间就是固定的,容量就是不可改变的,既不能在任何位置插入元素,也不能在任何位置删除元素,只能读取和修改元素,我们将这样的数组称为静态数组。
和其他变量的定义一样,数组变量定义的时候,也可以同时做初始化:

 

对于数组的初始化需要注意以下几点:

  1. 可以只给部分元素赋值。当{ }中值的个数少于元素个数时,只给前面部分元素赋值。例如:
    int a[10]={12, 19, 22 , 993, 344};
    表示只给 a[0]~a[4] 5个元素赋值,而后面 5 个元素自动初始化为 0。
  2. 只能给元素逐个赋值,不能给数组整体赋值。例如给 10 个元素全部赋值为 1,只能写作:
    int a[10] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
    而不能写作:
    int a[10] = 1;
  3. 如给全部元素赋值,那么在定义数组时可以不给出数组长度。例如:
    int a[] = {1, 2, 3, 4, 5};
    等价于
    int a[5] = {1, 2, 3, 4, 5};

    数组的定义,本质是根据数据长度分配对应个数的内存空间格子,一个格子放置一个数组元素,数组中的每个元素对应的内存格子都有一个序号,这个序号从0开始,而不是从我们熟悉的1开始,称为下标(Index)。
要获取数组元素时,我们通过数组变量名称+下标的形式:
arrayName[index],例如,a[0] 表示第0个元素,a[3] 表示第4个元素。

 

需要注意的是:

  1. 数组中每个元素的数据类型必须相同,对于int a[4];,每个元素都必须为 int。
  2. 数组长度 length 最好是整数或者常量表达式,例如 10、204 等,这样在所有编译器下都能运行通过;
    如果 length 中包含了变量,例如 n、4
    m 等,在某些编译器下就会报错,某些不会。
  3. 访问数组元素时,下标的取值范围为 0 ≤ index < length,过大或过小都会越界,导致数组溢出,
    发生不可预测的情况,我们要避免数组下标溢出。

二维数组

二维数组的定义、初始化、赋值、取值、遍历
数组可以看作是一行连续的数据,只有一个下标,称为一维数组。
在实际问题中有很多数据是二维的或多维的,因此C语言允许构造多维数组。多维数组元素有多个下标,以确定它在数组中的位置。
二维数组定义的一般形式是:
    dataType arrayName[length1][length2];
    我们可以将二维数组看做一个 Excel 表格,有行有列,length1 表示行数,length2 表示列数,要在二维数组中定位某个元素,必须同时指明行和列。

例如:int a[3][4];
a[0][0], a[0][1], a[0][2], a[0][3]
a[1][0], a[1][1], a[1][2], a[1][3]
a[2][0], a[2][1], a[2][2], a[2][3]

二维数组的初始化(赋值)
    二维数组的初始化可以按行分段赋值,也可按行连续赋值。

例如,对于数组 a[5][3],按行分段赋值应该写作:

 

按行连续赋值应该写作:

 

    这两种赋初值的结果是完全相同的。
单个运算赋值和取值:

 

二维数组遍历:
例: 用二维数据保存 5 个人,每个人有 3 门课程的考试成绩,求该小组各科的平均分和总平均分。

 

C语言字符数组和字符串
用来存放字符的数组称为字符数组,例如:

 

字符串定义和初始化:
C语言规定,可以将字符串直接赋值给字符数组,例如:

 

为了方便,你也可以不指定数组长度,从而写作:

 

    给字符数组赋值时,我们通常使用这种写法,将字符串一次性地赋值(可以指明数组长度,也可以不指明),而不是一个字符一个字符地赋值,那样做太麻烦了。
    这里需要留意一个坑,字符数组只有在定义时才能将整个字符串一次性地赋值给它,一旦定义完了,就只能一个字符一个字符地赋值了。
请看下面的例子:

 

字符串结束标志要我门注意
    C语言的字符串结尾标志的解决方案有点奇妙,或者说有点奇葩。
在C语言中,字符串总是以'0'作为结尾,所以'0'也被称为字符串结束标志,或者字符串结束符。

    '0'是 ASCII 码表中的第 0 个字符,英文称为 NUL,中文称为“空字符”。该字符既不能显示,也没有控制功能,输出该字符不会有任何效果,它在C语言中唯一的作用就是作为字符串结束标志。

    由" "包围的字符串会自动在末尾添加'0'。例如,"abc123"从表面看起来只包含了 6 个字符,其实不然,C语言会在最后隐式地添加一个'0',这个过程是在后台默默地进行的,所以我们感受不到。

 

要特别注意'0',要为'0'留个位置,进行指定长度的字符数组定义,字符数组的长度至少要比字符串的长度大 1:

 

    C语言为了提高效率,保证操作的灵活性,并不会对数组越界行为进行检查,即使越界了,也能够正常编译,只有在运行期间才可能会发生问题。在项目开发中,一定一定要注意,这点特别容易导致程序错误。

    另外需要注意的是,逐个字符地给数组赋值并不会自动添加'0',例如:

 

数组 str 的长度为 3,而不是 4,因为最后没有'0'。这样赋值我们建议:

 

另外,在定义字符串变量的时候,如不是定义的同时不需要赋有意义初始值,我们建议这样写:

 

string.h中声明的其他部分字符串处理函数
1)字符串长度函数 strlen()
所谓字符串长度,就是字符串包含了多少个字符(不包括最后的结束符'0')。例如"abc"的长度是 3,而不是 4。
在C语言中,我们使用string.h头文件中的 strlen() 函数来求字符串的长度,比如:

 

2)字符串连接函数 strcat()
strcat 意思是把两个字符串拼接在一起,语法格式为:
strcat(arrayName1, arrayName2);
arrayName1、arrayName2 为需要拼接的字符串。

    strcat() 的返回值为 arrayName1 的地址。

 

3)字符串复制函数 strcpy()
strcpy 意思是字符串复制,也即将字符串从一个地方复制到另外一个地方,语法格式为:
strcpy(arrayName1, arrayName2);
strcpy() 会把 arrayName2 中的字符串拷贝到 arrayName1 中,字符串结束标志'0'也一同拷贝。
请看下面的例子:

 

4)字符串比较函数 strcmp()
strcmp 意思是字符串比较,语法格式为:
strcmp(arrayName1, arrayName2);
arrayName1 和 arrayName2 是需要比较的两个字符串。

返回值:
若 arrayName1 和 arrayName2 相同,则返回0;
若 arrayName1 大于 arrayName2,则返回大于 0 的值;
若 arrayName1 小于 arrayName2,则返回小于0 的值。

 

函数

1、函数的定义和调用

1. C语言无参函数的定义
语法:

 

    dataType 是返回值类型,它可以是C语言中的任意数据类型,例如 int、float、char 等。
    functionName 是函数名,它是标识符的一种,命名规则前面讲过。
():放参数的地方,无参数就是括号里没东西,函数名后面的括号( )不能少。
    body 是函数体,它是函数需要执行的代码,是函数的主体部分。即使只有一个语句,函数体也要由{ }包围。
    如果有返回值,在函数体中使用 return 语句返回。
    return 出来的数据的类型要和 dataType 一致。
比如:

 
 

  括号里什么都没有和有void的区别
  这样我们就能知道,onePlusTwo()这种形式不是说onePlusTwo这个函数不允许带任何参数,而是指它带什么类型的参数,有多少个,是不确定的。
  而如果我们使用了onePlusTwo(void)这种形式,则就完全限定了onePlusTwo的参数是不允许带任何参数的。
2)无返回值函数的定义
语法格式:

 

void是C语言中的一个关键字,表示“空类型”或“无类型”,比如:

 

3)有参函数的定义
    如果函数需要接收用户传递的数据,那么定义时就要带上参数。语法:

 
 

有参函数的调用:

 

    函数定义时给出的参数称为形式参数,简称形参;函数调用时给出的参数(也就是传递的数据)称为实际参数,简称实参。函数调用时,将实参的值传递给形参,相当于一次赋值操作。

    原则上讲,实参的类型和数目要与形参保持一致。如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型,例如将 int 类型的实参传递给 float 类型的形参就会发生自动类型转换。

【示例】计算sum = 1! + 2! + 3! + ... + (n-1)! + n!
分析:可以编写两个函数,一个用来计算阶乘,一个用来计算累加的和。

 

2、库函数和自定义函数

    C语言在发布时已经为我们封装好了很多函数,它们被分门别类地放到了不同的头文件中声明(头文件可能只是申明没有定义),使用函数时引入对应的头文件即可。

    这些C语言自带的函数称为库函数(Library Function)。库(Library)是编程中的一个基本概念,可以简单地认为它是一很多函数的仓库,在磁盘上往往是一个文件夹。

    C语言自带的库称为标准库(Standard Library),其他公司或个人开发的库称为第三方库。

    除了库函数,我们还可以编写自己的函数,拓展程序的功能。自己编写的函数称为自定义函数。
3、C语言函数声明以及函数原型
    C语言代码由上到下依次执行,原则上函数定义要出现在函数调用之前,否则就会报错。
    但在实际开发中,经常会在函数定义之前使用它们,这个时候就需要提前声明。

 

    函数声明给出了函数名、返回值类型、参数列表(重点是参数类型)等与该函数有关的信息,。
  有了函数声明,函数定义就可以出现在任何地方了,甚至是其他文件、静态链接库、动态链接库等。
【实例1】定义一个函数 sum(),计算从 m 加到 n 的和,并将 sum() 的定义放到 main() 后面。

 

    然而在实际开发中,往往都是上千行、上万行、上百万行的代码,将这些代码都放在一个源文件中简直是灾难,不但检索麻烦,而且打开文件也很慢,所以必须将这些代码分散到多个文件中。

    对于多个文件的程序,通常是将函数定义放到源文件(.c文件)中,将函数的声明放到头文件(.h文件)中,使用函数时引入对应的头文件就可以,编译器会在链接阶段找到函数体。
4、C语言递归函数
一个函数在它的函数体内调用它自身称为递归调用,这种函数称为递归函数。
执行递归函数将反复调用其自身,每调用一次就进入新的一层,当最内层的函数执行完毕后,再一层一层地由里到外退出。

下面我们通过一个求阶乘的例子,看看递归函数到底是如何运作的。阶乘 n! 的计算公式如下:

 

 递归函数有巨大的时间开销和内存开销,有这种玩儿法,但不推荐大家在项目中去使用,一般情况下我们都是用循环去解决问题,你仔细想想其实据递归不就是另类的循环吗:

 

return 0和exit(0)的区别

简单来说:
exit(0):退出程序;
return 0:退出函数,返回值。

详细说:
1)return返回函数值,是关键字; exit 是一个函数。
2)return是语言级别的,它表示了调用堆栈的返回;而exit是系统调用级别的,它表示了一个进程的结束。
3)return是函数的退出(返回);exit是进程的退出。
4)return是C语言提供的,exit是操作系统提供的(或者函数库中给出的)。
5)return用于结束一个函数的执行,将函数的执行信息传出给其他调用函数使用;exit函数是退出应用程序。
6)非主函数中调用return和exit效果很明显,但是在main函数中调用return和exit的现象就很模糊,多数情况下现象都是一致的。

指针

1、什么是指针
   C语言用变量来存储数据,用函数定义一段可以重复使用的代码,它们最终都要放到内存中才能供 CPU 使用。
   不同类型的数据占用的字节数不一样,例如 int 占用 4 个字节,char 占用 1 个字节。
   为了正确地访问这些数据和指令,必须为每个字节空间上都编上号码,CPU运算时根据编号可以准确地找到对应的数据和指令,这个编号前面讲过称为内存地址(Address)。
    CPU 读取指令和数据时需要的是地址,不是变量名和函数名!
变量名和函数名只是地址的一种助记符,当源程序文件被编译和链接成可执行程序后,它们都会被替换成地址。
   假设变量 a、b、c 在内存中的地址分别是 0X1000、0X2000、0X3000,
那么加法运算c = a + b;将会被转换成类似的形式:0X3000 = (0X1000) + (0X2000);
  ( )表示取值操作,整个表达式的意思是,取出地址 0X1000 和 0X2000 上的值,将它们相加,把相加的结果保存到地址为 0X3000 的内存空间。
   普通变量名、函数名、字符串变量名和数组变量名在本质上是一样的,它们都是地址的助记符,在编写代码的过程中,我们认为普通变量名表示的是数据本身,而函数名、字符串变量名和数组变量名表示的是代码块或数据块的首地址。
      指针就是内存地址,是变量的地址,或函数的入口地址。
指针变量的定义和使用
   如果一个变量里储的值是另外一份存储数据的内存块的首地址,我们就称它为指针变量。
   指针变量的值就是保存了某份数据的内存空间的首地址,这样的一份数据可以是数组、字符串、函数,也可以是另外的一个普通变量或指针变量。
 定义指针变量
定义指针变量与定义普通变量非常类似,不过要在变量名前面加星号*,格式为:

 

或者

 

*表示这是一个指针变量,datatype表示该指针变量所指向的数据的类型 。例如:

 

p1 是一个指向 int 类型数据的指针变量,至于 p1 究竟指向哪一份数据,应该由赋予它的值决定。再如:

 
 

   是一个特殊符号,表明一个变量是指针变量,定义 p1、p2 时必须带
而给 p1、p2 赋值时,因为已经知道了它是一个指针变量,就没必要多此一举再带上

后边可以像使用普通变量一样来使用指针变量。
也就是说,定义指针变量时必须带,给指针变量赋值时不能带
   假设变量 a、b、c、d 的地址分别为 0X1000、0X1004、0X2000、0X2004,下面的示意图很好地反映了 p1、p2 指向的变化:

1IG3J50-1.jpeg

需要强调的是,p1、p2 的类型分别是float *和char *,而不是float和char,它们是完全不同的数据类型!


指针变量也可以连续定义,例如:

 


   指针变量存储了数据的地址,通过指针变量能够获得该地址上的数据,格式为:pointer;
这里的
称为指针运算符,用来取得某个地址上的数据,请看下面的例子:

 

假设变量 a、p_a 的地址分别为 0X1000、0XF0A0,它们的指向关系如下图所示:

clipboard (1).png

   程序被编译和链接后,a、p_a 被替换成相应的地址。使用 *p_a 的话,要先通过地址 0XF0A0 取得变量 p_a 保存的值,这个值是变量 a 的地址,然后再通过这个值取得变量 a 的数据,前后共有两次运算;而使用 a 的话,可以通过地址 0X1000 直接取得它的数据,只需要一步运算。
   也就是说,使用指针是间接获取数据,使用变量名是直接获取数据,前者比后者的代价要高。
   指针除了可以获取内存上的数据,也可以修改内存上的数据,例如:

 

‘*’总结:

1)定义指针变量时的和使用指针变量时的意义完全不同。

 

2)给指针变量本身赋值时不能加*。

 

3)指针变量也可以出现在普通变量能出现的任何表达式中。

 

关于 * 和 & 的谜题
   假设有一个 int 类型的变量 a,pa 是指向它的指针,那么&a和&pa分别是什么意思呢?
   &a可以理解为(&a),&a表示取变量 a 的地址(等价于 pa),(&a)表示取这个地址上的数据(等价于 *pa),绕来绕去,又回到了原点,&a仍然等价于 a。

&pa可以理解为&(pa),pa表示取得 pa 指向的数据(等价于 a),&(pa)表示数据的地址(等价于 &a),所以&*pa等价于 pa。

对星号的总结*
在我们目前所学到的语法中,星号*主要有三种用途:
1)表示乘法,例如int a = 3, b = 5, c; c = a * b;,这是最容易理解的。
2)表示定义一个指针变量,以和普通变量区分开,例如int a = 100; int *p = &a;。
3)表示获取指针指向的数据,是一种间接操作,例如int a, b, *p = &a; *p = 100; b = *p;。
3、指针变量的运算
   指针变量保存的是地址,而地址本质上是一个整数,所以指针变量可以进行运算,例如加法、减法、比较等,请看下面的代码:

 

从运算结果可以看出:pa、pb、pc 每次加 1,它们的地址分别增加 4、8、1,正好是 int、double、char 类型的长度;减 2 时,地址分别减少 8、16、2,正好是 int、double、char 类型长度的 2 倍。
----这很奇怪,指针变量加减运算的结果跟数据类型的长度有关,而不是简单地加 1 或减 1,这是为什么呢?
-----以 a 和 pa 为例,a 的类型为 int,占用 4 个字节,pa 是指向 a 的指针,如下图所示:


1J0563E7-0.jpeg

刚开始的时候,pa 指向 a 的开头,通过 *pa 读取数据时,从 pa 指向的位置向后移动 4 个字节,把这 4 个字节的内容作为要获取的数据,这 4 个字节也正好是变量 a 占用的内存。

如果pa++;使得地址加 1 的话,就会变成如下图所示的指向关系:

1J0-1.jpeg

这个时候 pa 指向整数 a 的中间,*pa 使用的是红色虚线画出的 4 个字节,其中前 3 个是变量 a 的,后面 1 个是其它数据的,把它们“搅和”在一起显然没有实际的意义,取得的数据也会非常怪异。

如果pa++;使得地址加 4 的话,正好能够完全跳过整数 a,指向它后面的内存,


1J05622E-2.jpeg

-----我们知道,数组中的所有元素在内存中是连续排列的,如果一个指针指向了数组中的某个元素,那么加 1 就表示指向下一个元素,减 1 就表示指向上一个元素,这样指针的加减运算就具有了现实的意义,这个有意义的应用我们后面在具体写demo来研究。

------不过C语言并没有规定变量的存储方式,如果连续定义多个变量,它们有可能是挨着的,也有可能是分散的,这取决于变量的类型、编译器的实现以及具体的编译模式,所以对于指向普通变量的指针,做加减运算没有意义,因为不知道它后面指向的是什么数据。

------指针变量除了可以参与加减运算,还可以参与比较运算。当对指针变量进行比较运算时,比较的是指针变量本身的值,也就是数据的地址。如果地址相等,那么两个指针就指向同一份数据,否则就指向不同的数据。

-----另外需要说明的是,不能对指针变量进行乘法、除法、取余等其他运算,除了会发生语法错误,也没有实际的含义。

4、数组指针

数组(Array)是一系列具有相同类型的数据的集合,每一份数据叫做一个数组元素(Element)。数组中的所有元素在内存中是连续排列的,整个数组占用的是一块内存。以int arr[] = { 99, 15, 100, 888, 252 };为例,该数组在内存中的分布如下图所示:

1J35014B-0.jpeg

定义数组时,要给出数组名和数组长度,数组变量名可以被认为就是一个指针,它指向数组的第 0 个元素。在C语言中,我们将第 0 个元素的地址称为数组的首地址。以上面的数组为例,下图是 arr 的指向:

2.jpeg

结合我们前面分析的指针变量的加减运算,循环遍历数组元素,我们还可以这样干:

版权声明


相关文章:

  • 零基础学java需要电脑吗2024-10-28 17:34:06
  • java基础测试题知乎2024-10-28 17:34:06
  • c语言基础下学java2024-10-28 17:34:06
  • java常用的基础2024-10-28 17:34:06
  • java基础不好从哪开始学2024-10-28 17:34:06
  • java基础7112024-10-28 17:34:06
  • java线程池基础知识2024-10-28 17:34:06
  • java面试问基础2024-10-28 17:34:06
  • java语言基础浮点布尔字符2024-10-28 17:34:06
  • 软件编程java零基础2024-10-28 17:34:06