C语言笔记

C基础

C语言标准格式

#include <头文件>  //预处理指令和头文件

int main(void)     //主函数有且只有一个,程序从这开始执行
{
    函数 a()     //函数
    {            //函数体的开始
        语句;
    }            //函数体的结束
    ...
    函数 n()
    
    return 0//写不写都可以,标准写法一般要写
}

注:C语言的基础模块是函数

注释

单行://
多行:/* 内容 */

语义和语法错误

语义:指“词不达意”,某些表达或者变量等不符合原有意思
语法:不符合C语言语法

数据和C


C语言的基本类型有:int、long、short、unsgined、signed、char、double、float、long double、_Bool、_Complex、_Imaginary。


int和float

int:一个int值普遍用32个字节存放(无论32位还是63位),存放不小于16位,取值范围为 -232~232-1
float:浮点数存放把浮点数分成小数和指数来分别储存。

初始化

声明是创建一个储存空间,初始化是创建空间并赋值。

int类型常量

一般来说只要是整数都是整型常量如果过大可能是long类型或者longlong类型。
int a = 1;

需要注意这里的常量值得是后面赋值的值,不是指前面标识符。也就是说a是变量,而1是常量。其他类型也同理。

打印int

print("%d",value);  
print("%ld",value);  
print("%hd",value);
print("%u",value);
print("%lu",value);
...

注:在打印过程中格式化字符串要与待打印变量一一对应

其他进制

打印八进制和十六进制分别用:%o和%x,要显示十六进制和八进制要加’#‘:%#o、%#x

其他整数类型

  • short int 比int占用少,至少用16位储存
  • long 取值比int大,32位储存,至少用32位储存(曾经int用16位储存现在也用32位,所以他俩基本取值范围一样)
  • longlong 为64位而生,,至少用64位储存
  • unsigned int 非负值取值为0~2^64
  • signed 可强调使用符号

整数溢出

如果达到它能表示的最大值以后,会重新重起点开始,无符号从零,有符号重他的最小取值开始。

字符

char类型用于储存字符,但是它是整数类型。实际上char储存的是整数。对应ASCII编码(每个字占据1bytes,范围位0~127),映射成字母。有些时候也不一定是ASCII编码,比如中文则以前较为常用是GB2312(每个字占据2bytes),现在则为GB18030(每个字占据4bytes)

字符常量

指后面赋值的单引号内的单个字符
char a = 'a';

注意:为字符常量赋值的时候一定是有’'括起来,双引号为字符串。

非打印字符

非打印字符指那些行为字符等的字符,例如转义字符既是。

常用转义字符
转义序列含义
\a警报
\b退格
\n换页
\t水平制表符

打印字符

printf()函数用%c格式化字符串打印的字符。

其他类型

_bool

其实也是一种整形,1表示真,0表示假

可移植类型:stdint.h和inttypes.h

stdint.h是一种可以移植的整数类型,为了在各系统中实现功能系统,而inttypes.h是对应的输出方式。了解即可

int32_t; //宽度为32位
int_least8_t; //最小容纳8位
int_fast8_t;  //运输最快的宽度为8的类型

float、double以及long double

浮点数包括:小数、科学计数法如:3.14、1.0e9(1.0X10的9次方)。

float至少能表示6为有效数字,取值2-37~237 。系统储存一个浮点数要占用32位,其中8位表示指数和符号,剩下24位表示非指数部分及其符号。

double至少能表示10为有效数字。系统储存一个浮点数要占用64位,其中8位表示指数和符号,剩下24位表示非指数部分及其符号。

long double则为更高精度。

float类型常量

浮点数都是实形常量或者浮点型常量。

打印浮点值

printf("%f");
printf("%e");
printf("%lf");
printf("%le");
printf("%la");
浮点值的上溢和下溢
  • 上溢:超过当前类型能表达的范围时
  • 下溢:损失原末尾有效位上的数字(损失全精度的浮点值)

复数和虚数类型

3种复数类型float_Complex、double_Complex、long double_Complex。
3种虚数类型float_Imaginary、double_Imaginary、long double_Imaginary。
注意:如果有complex.h头文件,可以用complex代替_Complex,虚数同理。

类型大小

sizeof是内置运输符,以字节为单位给出指定类型的大小,且用%zd输出,如果编译器不支持用%u、%lu。

stddef.h在包含stdio.h,把size_t定义成系统使用sizeof返回类型。

刷新输出

printf()语句把输出发送到一个**“缓冲区”**的中间储存区域,然后缓冲区的内容再不断发送到屏幕上。何时把缓冲区内容发送到屏幕:当缓冲区满、遇到换行字符、程序结束或者需要输入的时候(这个过程也叫刷新缓冲区)。

limits.h和float.h

这俩头文件分别提供了与整数和浮点数类型有关限制的相关信息。(具体在P67~68)

字符串和格式化输入/输出

字符串是多个字符的序列。C用数组储存字符串。数组由连续的存储单元组成,每个储存单元储存一个字符。每个字符串的结尾都有一个“\0”符号来结尾,所以在规划字符串大小时要多一个存储空间来储存“\0”。

使用字符串

%s来打印字符串。scanf()只会读取字符串中的一个单词,而非一整句,它在读到空格时就停止了,且存入变量不需要“&”符号。

strlen函数

strlen()函数给出字符串中的字符长度,用%zd输出,要加string.h头文件。输出的长度不是你字符串的储存空间,而是你实际字符串的长度不包括“\0”。

常量和C预处理器

在使用常量时我们可以使用“符号常量”,因为有些时候名字往往比数字更能表达具体意思。

float pi = 3.14;
float cicr = pi * 6;  //符号常量在计算时会被自动替换成所对应的值

那么如何创建符号常量,往往使用“明示常量”来创建

#define NAME value

注意明示常量的名字要全部大写(一个不成文的规定,你用小写也可以运行不会报错;或者在每次前贾c_和k_ ),语句末尾没有分号’

const限定符

const关键字,用于限定一个变量为只读。

printf()和scanf()

这俩函数是标准输入/输出函数(I/O函数)

printf()函数

printf(格式化字符串,待打印项1,待打印项2);
转换说明输出
%a/A浮点数、十六进制和p计数法
%e/E浮点数,e计数法
%f浮点数,十进制
%c字符
%s字符串
%d/i整数,十进制、有符号
%g/G根据值的不同,自动选择%f或%e/E。%e/E格式用于指数小于-4或者大于或等于精度时
%o无符号八进制整数
%x/X无符号十六进制,使用0f/0F
%p指针
%u无符号十进制整数
%%%

打印字符这些符号叫做“转换说明”
格式化字符串里的转换说明要与后面的每个项一一对应

printf()的转换说明修饰符
修饰符含义
标记后续表中查看具体内容
数字最小宽度,如果不够会自动扩容
.数字精度
h和整数一起使用表示短整型
hh和整数一起使用表示char类型
j和整数一起使用表示stdint.h中内容
l表示长整型
ll表示长长整型
L和浮点用,长双精度
t和整型一起用,ptrdiff_t俩指针插值类型
z和整数一起用,sizeof()返回类型

转换说明的意义在于把二进制格式储存的值翻译成特定计数法,不是替换。
转换不匹配,如果用无符号输出有符号最后是二进制补码,如果用数字输出字符且超过255会除于256并取余数

printf()中的标记
标记含义
-左对齐
+值为正在前面显示加号,负则显示负号
空格正数不显示,负数前面有负号
#把结果转换成另一种形式
0用0填充字段
printf()的返回值

返回打印字符个数。如果输出有误则为负数。

打印较长的值

打印较长的值的时候在""之间不能有隔断(回车)。但是有多种链接方式。

printf("dasfasfasf\
        sdasdasd");
printf("asdasdsad"
        "asdsadasd");

scanf()

  • 如果读取的是基本变量类型,在变量名前加&
  • 如果是字符串,不需要&

使用空白、换行符、制表符和空格分隔多个字段,但是%c会读取空格。

scanf()转换说明
转换说明输出
%e/E、%f/F、%g/G、%a/A把输入解释成浮点数
%d把输入解释成有符号十进制整数
%c把输入解释成字符
%s把输入解释成字符串
%i把输入解释成有符号十进制整数
%o把输入解释成有符号八进制整数
%x/X把输入解释成有符号十六进制
%p把输入解释成指针(地址)
%u把输入解释成无符号十进制整数
scanf()的修饰符
转换说明含义
*抑制赋值
数字最大字符宽度
hh把整数作为signed char或unsigned char类型读取
ll把整数作为long long或unsigned long long类型读取
h、l或Lh为short,l或L为long
j在整数前,表示使用intmaxt或uintmax_t类型
z表面使用sizeof()的返回类型
t两个指针差值的类型
从scanf()角度看输入
  • 如果使用字段宽度,是scanf()会在字段结尾或第1个空白字符处停止读取
  • 如果第1个非空白字符与转换说明不符合,就会停止读取,并把字符放回到输入中
  • 如果使用%s,scanf()会读取除空白以外的所有字符。
格式字符串的普通字符

除空格字符外的普通字符必须与输入字符严格匹配。

scanf("%d,%d",&a,&b);
12,24

除%c,其他转换说明会自动跳过带输入值前面所有空白。

scanf("%c",&ch);   //从第一个字符开始读取包括空白
scanf(" %c",&ch);  //从第一个非空白字符开始读取
scanf()的返回值

scanf()函数返回成功读取的项数。如果没有读取任何项,且需要读取一个数字而输入非数值字符串,便返回0。检测到“文件结尾”返回EOF(-1)。

printf()和scanf()的*修饰符

在print()中*用来代替字符宽度。

print("%*d",weight,num);     //第一个项是宽度对应*,第二个项才是打印数值

在scanf()中的*用来跳过相应输入项

scanf("%*d %d",&n);     //那么你输入的第一个数值并不会保存到变量,你的第二个数值才会保存

运算符、表达式和语句

基本运算符

C用运算符表示算数运算。

赋值运算符:=

=是赋值符号,赋值表达式的目的是把值储存到内存位置上。=号左侧必须是一个变量名,即一个储存位置。

几个术语:数据对象、左值、右值和运算符

用于储存数值的数据储存区域统称为数据对象。

左值:用于标识特定数据对象的名称或表达式

  • 它指定一个对象,可以引用内存中的地址

  • 它可用在赋值运算符的左侧

可修改左值/对象定位值:满足以上两点的

注:带const限定符的满足第一点,不满足第二点。它算左值但是不是可修改左值/对象定位值

右值/表达式的值:能够赋值给可修改左值的量,其本身不是左值

运算对象:被称为“项”,是运算符的操作对象

加法/减法运算符:+/-

用于加/减法计算。

符号运算符:+/-

用于表明一个值的代数符号(即正/负)

乘法运算符:*

用于乘法计算。

除法运算符:/

用于除法计算。在C语言中,整数除法结果的小数部分被丢弃,这一过程称为截断。

  • 除法会截断结果,不会四舍五入
  • 混合整数和浮点数计算,结果为浮点数(计算机一般会把整数转换成浮点数再计算)

负数的整数除法舍入过程采用小于或等于浮点数的最大整数,还有一种“趋零截断”直接舍弃小数部分。

运算符优先级

乘法和除法的优先级比加法和除法高。如果优先级相同则按顺序进行。

运算符优先级(从高至低)
运算符结合律
()从左往右
+ -(一元)从右往左
* /从左往右
+ -(二元)从左往右
=从右往左
优先级与求值顺序

当运算符共享一个运算对象时,优先级决定了求值顺序。结合律只适用于共享同一运算对象的运算符。

sizeof运算符和size_t类型

sizeof返回size_t类型的值。这是一个无符号整数类型

求模运算:%

只用于整数运算,不能用于浮点数。如果第一个运算对象时负数,那么求模结果为负数;如果第一个运算对象时正数,那么求模结果为正数。

递增/递减运算符:++/–

将其运算对象递增1或者递减1。分为前缀和后缀

++c;     //前缀先加后使用值
c++;	 //后缀先使用值后加

递增/递减有很高的结合优先级,只有圆括号别它们高。不要使用很多递增/递减运算符。

  • 如果一个变量出现在一个函数的多个参数中,不要使用
  • 如果一个变量多次出现在一个表达式中,不要使用
表达式和语句

表达式:表达式由运算符和运算对象组成,一些表达式由子表达式组成,且每个表达式都有一个值。

语句:是C语言的基本构件模块。一条语句相当于一条完整的计算机指令。大部分语句都以分号结尾。

声明:声明创建了名称和类型,并为其分配内存位置,且声明不是表达式语句。

赋值表达式语句:它为变量分配一个值。

函数表达式:引起函数调用。

wile语句是一种迭代语句,也称为结构化语句。

  1. 根据C的标准,声明不是语句。这和C++不一样
  2. 赋值和函数调用都是表达式
副作用点和序列点

副作用点:是对数据对象或文件的修改。

num = 2; //副作用将变量值设置为2

序列点:是程序执行的点,在该点上,所有的副作用都在进入下一步之前发生。语句的分号标记了一个序列点。

完整表达式:指这个表达式不是另一个更大表达式的子表达式。

复合语句(块)

用花括号括起来的一条或多条语句,也称为块

类型转换

C语言会自动转换类型

  1. unsigned/signed的char和short转成int,有必要会转成unsigned int;float会转为double
  2. 涉及两种类型的运算,两个值会被分别转换成两种类型的更高级别
  3. 在赋值表达式语句中,计算机的最终结果会被转换成被赋值变量的类型
  4. 当作为函数参数传递时,char和short转成int;float会转为double

待赋值与目标类型不匹配时:

  1. 目标类型是无符号整数,且待赋值是整数时,额外的位将被忽略。
  2. 目标类型是有符号整数,且待赋值是整数时,结果因实现而异。
  3. 目标类型是一个整数,且待赋值是浮点数时,该行为未被定义。
强制转换

(类型)变量

带参数的函数

声明参数就创建了被称为形式参数的变量。我们称函数的调用传递的值为实际参数,简称实参。变量名是函数私有的,即在函数定义的变量名不会和别处的相同名称发生冲突。原型即是函数的声明,描述函数的返回值和参数;函数声明只指明了函数名和返回类型,没有指明参数类型。

循环

while循环

格式如下:

while(表达式)
{
	语句;
}

每次循环都被称为一次迭代

终止while循环

while循环有一点非常重要:在构建while循环时,必须让测试表达式的值有变化,表达式最终要为假。否则,循环就不会终止。

最大正值加1一般会得到一个负值,最小负值减1会得到最大正值

while:入口条件

while循环是使用入口条件的有限循环。所谓“条件”指的是语句部分的执行取决于测试表达式描述的条件,该表达式则为入口条件。

单独分号是空语句

用关系运算符和表达式比较大小

while循环经常依赖测试表达式作比较,这样的表达式被称为关系表达式,出现在关系中间的运算符叫做关系运算符。

运算符含义
<小于
<=小于等于
>大于
>=大于等于
==等于
!=不等于

新的_Bool类型

_Bool类型的变量只能储存1(真)和0(假)。如果把其他非零数值赋值给其类型的变量,该变量会被设置为1。C99提供了stdio.sh头文件,该头文件让bool称为_bool的别名,而且把true和false分别定义为1和0的符号常量。

优先级和关系运算符

运算符(优先级从高到低)结合律
()从左往右
- + ++ – sizeof从右往左
* / %从左往右
+ -从左往右
< > <= >=从左往右
== !=从左往右
=从右往左

for循环

for(表达式1;表达式2;表达式3);{
	语句;
}

表达式1是初始化,只在循环开始执行一次;表达式2是测试条件,在执行循环之前对表达式求值;表达式3是执行更新,在每次循环结束时求值。

表达式2执行完后先执行语句,再执行表达式3

逗号运算符

  • 它保证了被分割的表达式从左往右求值(逗号是个序列点)
  • 整个逗号表达式的值是右侧项的值
x=(y=3,(z=++y + 2)+5);   //x=11,右侧项的值

do while循环

do
{
	语句;
}
while(表达式);

出口条件循环,即在循环的每次迭代之后检查测试条件。即先执行语句,再判断真假,所以就算循环在第n次结束,也会输出第n次的内容,而while循环只会输出n-1次的内容。

数组

数组是按顺序储存的一些列相同类型的值,且以整数下标(从0开始计数)访问数组中单独的项或元素。

类型 数组名[容量];

注意下标索引数组内容时不要越界,即数组容量为n,但其下标为0~(n-1),第n个项的下标为n-1。

分支和跳转

if语句

if(条件)
	语句;

if语句为分支语句或选择语句,它相当于一个交叉点,程序要在两条分支中选择一条执行。如果条件为真(非0),则执行语句;否则,跳过语句。

if else语句

if(条件)
{
	语句1}
else
	语句2

与if不同之处为,条件为真执行语句1,假则执行语句2。

ctype.h系列的字符函数

用于判断某些特定的值,如果该字符属于某特殊的类别,就返回一个非零值(真);否则,返回0(假)。

函数名参数为真
isalnum()字母数字
isalpha()字母
isblank()标准空白字符(空格、水平制表、换行)或任何本地化指定空白符
iscntrl()控制字符
isdigit()数字
isgraph()除空格外的任意可打印字符
islower()小写字母
isprint()可打印字符
ispunct()标点符号(除空格或字母数字)
isspace()空白字符(空格、换行、换页、回车、垂直制表、水平制表或其他本地化定义)
isupper()大写字母
isxdigit()十六进制数
函数名行为
tolower()如果参数是大写字符,该函数返回小写字符;否则,返回原始参数
toupper()如果参数是小写字符,该函数返回大写字符;否则,返回原始参数

多重选择else if

if(条件1)
{
	语句1}
else if(条件2)
{
	语句2}
else
	语句3

与上面不同之处为,条件1为真执行语句1,假则判断是否符合条件2,真则执行语句2,假则执行语句3。

如果没有花括号,else与离他最近的if匹配,除非最近的if被花括号括起来。

逻辑运算

逻辑运算符含义
&&
||
!
  • 表达式1和表达式2都为真时,表达式1 && 表达式2才为真
  • 表达式1或表达式2为真时,表达式1 || 表达式2才为真
  • 表达式1为假,则!表达式1为真;反之则为假

iso646.h头文件

替换原运算符
and&&
or||
not!

条件运算符: ? :

表达式1?表达式2:表达式3

如果表达式1为真则取表达式2的值;如果为假则取表达式3的值。

循环辅助:continue和break

  • 执行到continue语句时,会跳过本次迭代的剩余部分,并开始下一轮迭代
  • 执行到break语句时,会终止包含它的循环,并继续执行下一阶段

多重选择:switch和break

switch(整型表达式)
{
	case 常量1:
	语句1case 常量2:
	语句2default:
	语句3}

对switch后圆括号内的表达式求值,然后扫描标签(case),直到找到一个匹配的值并执行其语句内容;如果没有则执行default的语句;break要与switch一起使用避免进入死循环。

多重标签

switch(整型表达式)
{
	case 常量1:
	case 常量2:语句1case 常量3:
	case 常量4:语句2default:
	语句3}

对switch后圆括号内的表达式求值,如果值与标签1和标签2相同则输出语句1,其余则相同。

goto语句

C语言可以使用,但尽量避免使用。

goto 标签名字;
		.
		.
		.
标签名字:语句;

字符输入/输出和输入验证

缓冲区

输入的字符被收集并储存在一个被称为缓冲区的临时储存区,按下ENTER键后,程序才可以使用用户输入的字符。如果没有缓冲区输入的字符将会立马被输出。缓冲分为两类:完全缓冲I/O和行缓冲I/O。完全缓冲输入指的是缓冲区被填满时才刷新缓冲区域;行缓冲指的是在出现换行符是刷新缓冲区。

conio.h头文件

包括回显无缓冲输入getche()和无回显无缓冲输入getch()。

结束键盘输入

文件、流和键盘输入

文件是存储器中储存消息的区域。流是一个实际输入或输出映射的理想化数据流,这意味着不同属性和不同种类的的输入,由属性更统一的流来表示。C把输入和输出设备视为储存设备上的文件,尤其是把键盘和显示设备视为每个C程序自动打开的文件。

stdin流表示键盘输入,stdout流表示屏幕输出。

文件结尾

在C语言中,用getchar()读取文件检测到文件结尾时将返回特殊值EOF。scanf()也返回相同值

EOF通常情况下等同于-1

#define EOF (-1)

单字符I/O:getchar()和putchar()

俩函数只输出或输入一个字符。

  • getchar()的返回值是用户输入的第一个字符的ASCII码
  • putchar()的返回值返回的是原字符,但如果输入一连串字符,则只会返回第一个字符

重定向

重定向输入

./echo_eof < words

<是输入重定向运算符

重定向输出

./echo_eof > mywords

>是输出重定向运算符

  • 重定向运算符链接一个可执行程序(包括标准操作系统命令)和一个数据文件,不能用于链接一个数据文件和另一个数据文件,也不能用于链接一个程序和另一个程序
  • 使用重定向运算符不能读取多个文件的输入,也不能把输出定向到多个文件
  • 文件名和运算符之间的空格不是必须的

函数

是完成特定任务的独立程序代码单元。

创建使用函数

函数类型 函数名(参数类型1 形参1,参数类型2 形参2);   //函数原型
int main(void)
{...
函数类型 函数名(参数类型1 实参1,参数类型2 实参2)//函数使用
...}
函数类型 函数名(参数类型1 形参1,参数类型2 形参2)   //定义函数
{...}
  • 函数原型告诉编译器函数的类型;函数调用表明在此处执行函数;函数定义明确地指定了函数要做什么
  • 任何函数在使用之前都要声明
  • 函数原型指明了函数的返回值类型和函数接受的参数类型。这些信息称为该函数的签名
  • 函数类型值是返回类型值,不是参数类型值
  • 被调函数只能返回一个值到主调函数中
  • 如果两个参数都是数字,但类型不匹配,会强制转换成形参类型

函数的声明

函数类型 函数名(参数类型1 形参1,参数类型2 形参2)
{参数类型1 形参1,参数类型2 形参2}

函数类型 函数名(形参1,形参2)
{参数类型 形参1,形参2}

函数类型 函数名(参数类型1,参数类型2)
{参数类型1 形参1,参数类型2 形参2}

函数的返回值

函数通过return从被调函数中返回值到主调函数中,返回的值不一定是变量的值,也可以是任意表达式,且返回值类型要和函数类型一致。

int Imin(int x,int y)
{
	if(n<m)
		return n;
	else
		return m;
}

int Imin(int x,int y)
{
	return (n<m)?n:m;
}

递归

函数自己调用自己称为递归

void up_and_down(int n)
{
	printf("%d ",n);
	if(n<4)
		up_and_down(n+1);
	printf("%d ",n);
}
  • 每级的递归的变量都是私有的
  • 每级调用都会放回一次,按顺序逐级返回递归
  • 递归函数中位于递归调用之前的语句,均按被调函数顺序执行
  • 递归函数中位于调用之后的语句,均按被调函数相反的顺序执行

编译文件

cc file1.c file2.c  //编译俩文件
cc file1.c file1.o  //编译修改后的文件

Linux把cc换成gcc,DOS把.o换成.obj

数组

数组由一系列相同数据类型的元素组成,数组由下标索引,索引编号由0开始而不是1

初始化数组

数组类型 数组名字[数组容量] = {数组元素};

  • 数组使用前必须初始化
  • 如果数组列表中和数组大小不一致,之后的元素都初始化为0
  • 如果没规定数组大小,数组大小将有项数来自动确定大小
指定数组大小

声明数组时只能在方括号里使用整形变量。sizeof时整形变量,但const不是。

C99指定初始化器

数组类型 数组名字[数组容量] = {[数字下标] = 元素};

  • 如果指定初始化后有更多值,那么后面的值将被用于指定初始化后面的元素
  • 如果再次指定初始化,之前的值将会被替代

多维数组

数组类型 数组名字[数组个数][数组容量] = {{数组1元素1,数组1元素2},{数组2元素1,数组2元素2}};

  • 如果某一组数组的数组容量超出,则会出错,但不运行其他组的初始化
  • 可以省略数组个数的标定,但是不能省略数组容量的标定
  • 如果初始化数值不够,则按照先后顺序初始化,直达用完为止,后面不够的用0初始化

指针

查找地址运算符:&和间接运算符:*

  • &用于查找地址
  • *用于找出地址中的值,也成为解引用计算符

声明指针

指针类型 * 指针名字;

*和指针名字之间的空格可有可无。

不要解引用未初始化的指针

指针和数组

数组名字&数字名字[0]都表示数组首元素的内存地址。二者都是常量。

dates + 2 == &dates[2]
*(dates + 2) == dates[2]
*dates + 2 == dates[0]+2

对数组而言,数组的指针+1,意味着下一个元素的地址,而不是下一个字节的地址

  • 指针的值是它指向对象的地址
  • 指针前用*可以得到该所指对象的值
  • 指针加1,指针的值递增他所执行类型的大小(字节)

函数、数组和指针

只有在函数头或函数定义中,才可以使用int arr[]代替int* arr,也就是说int arr[]只能用于声明

声明数组形参

//函数原型
int sum(int* arr,int n);
int sum(int* ,int );
int sum(int* arr[],int n);
int sum(int* [],int n);
//定义
int sum(int* arr,int n)
{...}
int sum(int arr[],int n)
{...}

数组指针的运用

  • C保证在给数组分配空间时,指向数组后面第一个位置的指针仍然是有效指针
  • 只用当数组是指针变量时才能使用指针操作

指针操作

操作含义
赋值(=)把地址赋给指针
解引用(*)给出指针指向地址的值
取址(&)指针变量也有自己的地址,即&ptr指向ptr,ptr指向arr[0]
指针与整数相加往后移动相加数个元素
指针与整数相减往前移动相减数个元素(第一个运算对象必须是指针)
递增往后移动一个元素
递减往前移动一个元素
指针求差计算求两个元素之间的距离
比较比较两个指针的值(指针类型相同)

const在指针的使用

const int* n = 10;
*n = 12; //不允许
n++;     //允许
int* const n2 = 10;
*n2 = 20;  //允许
n2++;      //不允许
const int* const n23 = 10;
*n2 = 20;  //不允许
n2++;      //不允许
  • 在声明或初始化时const在最前方不允许修改指针的值
  • 在声明或初始化时const在变量名前方不允许修改指针指向的方向
  • 双const时不能修改指针任何东西
  • 可以把const数据或非const数据赋值给const的指针
  • 普通指针只能使用非const数据

多维数组和指针

int arr[2][2] = {{1,2},{3,4}};
  • arr和&arr[0]的值相同
  • 给指针加1,其值会增加对应类型的大小的数值
  • 接应用一个指针或在数组名后使用带下标的[]运算符,得到引用对象的值
  • **arr == *&arr[0][0]
arr;             //首地址
arr+1//第二组数组的地址
*(arr+1);        //第二组数组内首元素的地址
*(arr+1)+1;      //第二组数组的第二个元素的地址
*(*(arr+1)+1);   //第二个数组的第二个元素的值

指向多维数组的指针

数组类型 (*数组名)[单组数组的容量];

[]运算符的个数取决于数组的维度,一个为二维,两个为三维,以此类推

arr[m][n] == *(*(arr+m)+n)

指针的兼容性

  • 指针之间的赋值时类似必须一致
  • 指针和多维数组间赋值的时候指针指向多维数组类型的个数必须与数组元素的个数一致
  • 双指针与二位数组不能赋值,**pp;pp = arr; //错误 pp是指向指针的指针,它指向的指针指向int;而arr是指向数组的指针,内涵2个int元素
  • 标准规定非const指针更改const数据未被定义,所以把const值赋给非const指针不安全

变长数组

在C99以前C数组的维度必须是常量,不能是变量。C99新增了变长数组,允许使用变量表示数组的维度。变长数组中的“变”不是指可以修改已创建数组的大小。一旦创建了变长数组,它的大小则保持不变。

int reg,qua;
int arr_vla[reg][qua];

C99/C11规定,可以省略原型中的形参名,但是必须用星号代替省略维度:int fun(arr[*][*])

  • 在函数定义的形参列表中声明的变长数组并为实际创建数组;变长数组名实际是一个指针;变长数组形参的函数实际是在原始数组中处理数组
  • C99以前数组的大小必须是给定的整形常量表达式,可以是整形常量组合;之后才可以使用const变量
  • 变长数组允许动态分配内存,普通数组是静态内存

复合字面量

字面量是除符号以外的常量。

(int [2]) = {10,20}是符合字面量。在初始化时可以省略容量大小。因为符合字面量时匿名的,所以不能先创建再使用,必须在创建的时候就使用。

字符串和字符串函数

字符串和字符串I/O

  1. 字符串字面量:用双引号括起来的内容称为字符串字面量,也叫字符串常量。如果字符串字面量之间没有间隔,或者用空白字符隔开,C会将其视为串联起来的字符串字面量。

    char s1[50]="Hello""World"
    char s2[50]="Hello World"
    

    字符串属于静态储存类别。用双引号括起来的内容被视为指向该字符串储存位置的指针

  2. 字符串数组和初始化:在指定数组大小,要确保数组的元素个数至少比字符串长度多1(为容纳空字符)。让编译器计算数组大小只能用在初始化数组时;如果创建一个稍后再填充数组,就必须在声明时指定大小。

  3. 数组和指针:

    数组形式:字符串有两个副本。一个是静态内存中的字符串字面量,另一个是储存在数组中的字符串。编译器把数组名识别为该数组的首元素地址,即数组名是地址常量。如果更改数组名,则改变了数组的储存位置。

    指针形式:也会在静态储存区预留空间。该变量最初指向该字符串的首字符。字符串字面量被视为const数据,所以要把它声明为const指针。

    初始化数组把静态储存区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针。

  4. 数组和指针的区别

    区别:1)数组名是常量(数组元素是变量),指针名是变量;2)两者都能进行指针加法操作,但是只用指针表示法可以进行递增操作

    编译器可以使用内存中的一个副本来表示完全相同的字符串字面量,所以在指针初始化的时候设定const限定符。所以打算修改字符串不要使用指针指向字符串。

  5. 字符串数组:在初始化是数组会在从静态存储区拷贝内容过来,而指针则是拷贝地址过来,所以使用数组内存占用大,但是如果要改变字符串或为字符串留空间则不要使用指针。

字符串输入

第一件事情就是分配空间。计算机不会在读取字符串时顺便计算长度,然后再分配空间,所以一般要显式声明数组大小。

gets()函数

读取整行输入,直至遇到换行符,然后丢弃换行符,存储其余字符,并在末尾添加一个空字符。如果缓冲溢出会擦写数据

gets(words);

fgets()函数

  • 函数的第二个参数指明了读入字符的最大数量
  • 该函数不会丢弃换行符,会保留
  • 第三个参数指明要读入的文件,一般为stdin。

fgets(读入内容,最大限制,stdin)

fgets()函数返回指向char的指针。如果一切顺利,该函数返回的地址与传入的第一个参数地址相同。如果读到文件结尾,它将返回空指针(空指针不指向任何内存,在代码中可用NULL和0代替)。fgets()一次读入限制长度-1个字符

空字符和空指针

空字符(\0)用于标记字符串末尾的字符,其对应编码为0,整数类型,1字节,一个字符

空指针有一个值,该值不会与任何有效数据的地址对应,指针类型,4字节,一个地址

gets_s()函数
  • 只从标准输入读取数据,所以不需要第三个参数
  • 遇到换行符会丢弃不会保存
  • 如果读到最大字符限制都没读到换行符:首先把目标数组的首字符设置为空字符,读取并丢弃随后的输入直至读到换行符或文件结尾,然后返回空指针;接着,调用依赖实现的“处理函数”(或你选择的其他函数),可能会中止或退出程序。

scanf函数

scanf()函数读入数据像“获取单词”,它读取到第一个空白字符或限制最大数就停止

字符串输出

puts()函数

只需要把字符串地址作为参数传给他即可

puts(words);

puts()会在显示时自动在其末尾添加一个换行符。在遇到空字符就停止输出。

fputs()函数

  • 第二个参数是指明要写入数据的文件,一般为stdout
  • 不添加换行符在末尾

fputs(words,stdout);

printf()函数

与puts()一样,把字符串的地址作为参数。但不会自动加入换行符,必须手动添加。

字符串函数

strlen()

用于统计字符串长度

strcat()函数

用于拼接俩字符串。把第二个字符串的备份加在第一个字符串末尾,然后把新字符串保留在第一个字符串,第二个字符串保持不变。

strncat()函数

strcat()无法检测第一个数组是否能容纳下第二个字符串。所以添加了第三个参数指定最大字符数。

strcmp()函数

比较字符串。如果两个字符串参数相同,该函数返回0,否则返回非0值。

返回值

如果字母表中第一个字符串位于第二个字符串前面返回负数;反之为正数,两者ASCII码之差。在ASCII码中大写字母在小写字母前面。

strncmp()函数

strcmp()函数是整个字符串的比较直至不同为止,二strncmp()函数可以比较字符不同的地方,也可以只比较第三个参数指定的位置。

strncmp(数组,查找内容,字符限制);

strcpy()和strncpy()函数

如果要拷贝整个字符串,要使用strcpy()函数。它把第二个参数指向的字符串拷贝到第一个参数指向的数组中。拷贝完的字符串被称为目标字符串,最初的字符串称为源字符串。

strcpy()的其他属性
  • 返回类型为char *,该函数返回第一个参数的值
  • 第一个参数不必指向数组开始
  • 空字符也拷贝在内
strncpy()函数

该函数的第3个参数指明可拷贝的最大字符限制。它把最大字符限制之前的字符或空字符拷贝到目标字符串,如果字符数小于最大限制则拷贝这个字符串和空字符,反之则拷贝到最大字符限制且不拷贝空字符

sprintf()函数

sprintf()函数把数据写入字符串,而不是输出。sprintf()函数的第一个参数是目标字符串的地址,把格式化字符拼接,保存在目标字符串中。

sprintf(目标字符串,“%s,%s”,项1,项2);

命令行参数

命令行参数是同一行的附加项。

$ fuss -r Ginger

C编译器允许main()没有参数或者有两个参数。第一个参数是命令行中的字符数量,为int类型称为argc;第二个参数用于储存数组的地址,这里的数组是命令行字符串存储在内存中,并把每个字符串的地址储存在指针数组中。称为argv。

许多环境允许用双引号把多个单词括起来形成一个参数

把字符串转换为数字

atoi()把字母转换成整数
atof()把字母转换成浮点数
atol()把字母转换成双精度浮点数

如果argc<2,就不会对以上函数求值。

strtol()把字符串转换为长整型值
strtoul()无符号长整形
strtod()双精度浮点数

strtol()和strtoul()用法

long strtol(const char* restrict nptr,char ** restrict endptr,int base );

nptr是指向待转换字符串的指针,endptr是一个指针的地址,该指针被设置为标识输入数字结束字符地址,base为以什么进制写入数字。

strtod只需要两个参数

储存类别、链接和内存管理

储存类别

被储存的每个值都占用一定的物理内存,C语言把这样的一块内存称为对象。对象可以储存一个或多个值。一个对象可能并未储存实际的值,但是它在储存适当的值时一定具有相应的大小(面向对象编程中的对象指的是类对象,其定义包括数据和允许对数据进行操作,C不是面向对象编程语言)。可以用存储区描述对象,所谓存储期是指对象在内存中保留多长时间。

作用域

作用域描述程序可访问标识符的区域。

  • 块是用一对花括号括起来的代码区域。块作用域变量的可见范围是从定义处到包含该定义的块末尾。另外,虽然函数的形式参数声明在函数花括号前,但是它们也具有块作用域,属于函数体这个块。局部变量(包括函数的形参)都具有块作用域。
  • 函数作用域仅用于goto语句的标签。一个标签首次出现在函数内层块中,它的作用域也延申至整个函数
  • 函数原型作用域用于函数原型的形参名。函数原型作用域的范围是从形参定义处到原型声明结束。(只用在变长数组中,形参名才有用,要与函数定义中一致)
  • 定义在函数外面的变量,具有文件作用域。从它定义到结尾均可见。也称为全局变量

翻译单元和文件

编译器把源代码文件和所有的头文件都看成是一个包含信息的单独文件。这个文件被称为翻译单元。

链接

C有3种链接属性:1)外部链接、2)内部链接、3)无链接。

  • 无连接:具有块作用域、函数作用域或函数原型作用域的变量都是无链接变量。这意味着这些变量是私有的。
  • 外/内部链接:具有文件作用域的变量可以是外部链接或内部链接。外部链接变量可以在的文件出现使用,内部链接变量只能在一个翻译单元使用。

内部链接的文件作用域简称为文件作用域,外部链接文件作用域简称为全局作用域或程序作用域

int n = 1;           //文件作用域,外部链接
static int n2 = 2;   //文件作用域,内部链接

存储期

作用域和链接描述了标识符的可见性。存储期描述了通过这些标识符访问的对象的生存期。C对象有4种存储期:静态存储期、线程存储期、自动存储期、动态分配存储期。

  • 静态存储期:如果对象具有静态存储期,那么它在程序的执行期间一直存在。文件作用域变量具有静态存储期。以static声明的文件作用域具有内部链接。但是无论是内部链接还是外部链接,所有文件作用域变量都具有静态存储周期。
  • 线程存储期:用于并发程序设计,程序执行可被分为多线程。具有线程存储周期的对象,从被声明到线程结束一直存在。以关键字_Thread_local声明一个对象时,每个线程都获得该变量的私有备份。
  • 块作用域的变量通常都具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存;当退出这个块时,释放刚才为变量分配的内存。块作用域也能有静态存储期,在声明前加上static关键字。

变长数组稍有不同,它们的存储期从声明到块的末尾,而不是从块的开始处到块的末尾

存储类别存储期作用域链接声明方式
自动自动块内
寄存器自动块内,使用关键字register
静态外部链接静态文件外部所有函数外
静态内部链接静态文件内部所有函数外,使用关键字static
静态无链接静态块内,使用关键词static

自动变量

属于自动存储类别的变量具有自动存储期、块作用域或且无链接。可以显式使用关键字auto。变量具有自动存储期意味着,程序在进入该变量声明所在块是变量存在,程序在退出该块变量时变量消失。

auto关键字是存储类别说明符。auto关键字在C++的用法完全不同,如果写C/C++兼容程序最好不要使用auto关键字

内层块会隐藏外层块的含义。但是离开内存块后,外层块变量的作用域又回到了原来的作用域。

int main()
{
	int x = 30;    //原始x
	for(...)
	{
	...
	int x = 10;    //新的x,隐藏了原始x
	...
	}
	while(x++//原始x
	{...}
}
  1. 没有花括号的块

    整个循环时它所在块的子块,循环体时整个循环块的子块

    int n = 10;             //原始n
    for(int n=1,n<3,n++)    //新的n,隐藏了原始n,并作用至循环末尾
    	printf("%d",n);
    
  2. 自动变量的初始化

    自动变量不会初始化,除非显式初始化它。可以用非常量表达式初始化自动变量,前提是所用变量已在前面定义过。

    int n = 1;
    int n2 = n * 1;
    

寄存器变量

访问和处理变量速度更快,无法获取寄存器变量的地址。绝大多数方面,寄存器变量和自动变量一样。它们都是块作用域、无链接和自动存储期。使用存储类别说明符register声明。编译器必须根据寄存器或最快可用内存的数量衡量你的请求,或者直接忽略你的请求,所以可能不会如你愿。在这种情况下,青春期变量就变成普通的自动变量。即使这样不能对该变量使用地址运算符。次类型变量不能使用较大的数据类型例如double,因为寄存器存储小。

块作用域的静态变量

静态变量的意思是该变量在内存中原地不动,并不是说值不变。具有文件作用域的变量自动具有静态存储期。

int statval(void{
	...
	static int num = 1...
}

不能再函数的形参中使用static:

int try(static int n); //不允许

局部静态变量是描述具有块作用域的静态变量的另一个术语。或称为内部静态存储类别,这里内部指函数内部。

外部链接的静态变量

外部链接的静态变量具有文件作用域、外部链接和静态存储期。也称为外部变量。把变量的定义性声明放在所有函数的外面便创建了外部变量。如果源代码文件使用的外部变量定义在另一个源代码文件中,则须使用extern再次声明。

int n;            //外部定义的变量,具有静态存储期
extern int n2;    //其他文件中的变量
int main()
{
	extern int n; //可以去掉直接使用n,如果没有extern关键字那么就创建了一个自动变量,隐藏了原始n
}
  1. 初始化外部变量

    与自动变量不同的是,如果未初始化外部变量,它们会被自动初始化为0.这一原则也适用于外部定义的数组元素。与自动变量的情况不同,只能使用常量表达式初始化文件作用域。

    int x = 10;                //10是常量
    int y = 3+20;              //常量表达式
    size_t z = sizeof(int);    //常量表达式
    int x2 = 2*x;              //不可以,x是变量
    
  2. 使用外部变量

    使用外部变量如果不在本文件中一定要使用extern引用变量,如果在本文件中在其他块作用域的时候可以使用extern声明变量也可以直接使用变量。

  3. 外部名称

    C99和C11标准要求识别局部标识符的前63个字符和外部标识符的前31个字符(以前是局部31和外6)

  4. 定义和声明

    第一次声明叫定义式声明,第二次声明叫引用式声明。extern关键字表示该声明不是定义。外部变量只能初始化一次且必须在定义时进行。

内部链接的静态变量

该存储类别的变量具有静态存储期、文件作用域和内部链接。要使用static说明符。过去叫外部静态变量,现在叫内部链接静态变量。

int n0;             //外部链接
static int n = 1;   //内部链接
int main()
{...}

多文件

除了定义声明外,其他声明都要使用extern关键字,而且,只有定义式声明才能初始化变量。如果外部变量定义在一个文件中,其他文件使用它都得先声明它。

存储类别说明符

说明符存储期作用域链接
auto自动块作用域内部/无
register自动块作用域
static静态文件作用域外部/内部/无
extern静态文件作用域外部/内部

存储类别和函数

函数也有存储类别,可以是外部函数(默认)或静态函数。C99新增了第3种类别—内联函数

double A(double);            //可以被其他文件使用
static int B(int b);         //只能在本文件中使用
extern double C(double c);   //使用其他文件的函数

随机函数和静态变量

  • rand()函数生成随机数,但是是“伪随机数生成器”。该函数使用该种子生成新的数,这个新的数又成为新的种子。然后,新种子可用于生成更新的种子。随机数必须记录它上一次被调用时所使用的种子。这里需要一个静态变量。范围0~32767。
  • srand()函数用于重置种子。如果不重置种子,随机数可能与上一次随机数相同。
  • 自动重置种子:time()函数返回系统时间。返回类型为time_t。初始化种子为:srand((unsigned int)time(0));。time()接受的参数是一个time_t类型对象的地址,而时间值就存储在传入的地址中。

分配内存:malloc()和free()

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是济槐呀(花开富贵版)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值