C语言学习笔记

提示:本人的C语言学习笔记,包含记录一些自己在写代码时遇到的坑。若有问题欢迎大家评论指出

文章目录

  • 其他
  • 进制间的转换
  • 位操作
  • 头文件操作
  • 转义字符
  • 函数
  • 运算符
  • 循环语句
  • 基本数据类型
  • 字符串输入与输出函数
  • 数组
  • 结构
  • IO
  • 指针



其他

//vs使用scanf函数会出警告,增加这段代码即可:#pragma warning(disable:4996)
//vs快捷键 CTRL+K注释 Ctrl + F5运行
//NULL==0
//连续存储的变量的地址是连续的,下一个地址等于上一个地址加下一个元素的大小(sizeof)
//在创建数组时,如果不给数组设置初始值就打印会打印出内存中遗留的”脏数据“
在vs的默认编码中,a到z递增,英文大于数字,空最小
一个程序可以使用多个源代码文件,但这多个源代码文件其实可以看做一个源代码文件分割为多个文件,其中有一个主要的执行文件(main函数在的地方)
,其他文件都是用于定义函数的
—编译器是将代码转变为计算机可识别的语言(目标代码)。链接器是将目标代码和启动代码和库函数合成可执行代码
—不能在main函数中定义和声名函数
—有效的名称:大小写,数字,下划线。第一个字符必须是字母或者下划线(c区分大小写)
—地址不是左值(不能作为左值),左值是一个可以修改的值,指针可以作为左值
对象是储存了某些数据的一块内存(和Java中的对象不同)
------作用域:声名在文件一开始的对象是外部链接对象:可以在多文件程序使用(该对象不能初始化为某一可变的对象(即只能初始化为常量或者常量表达式))
内部链接对象只能在一个源文件和他所包含的头文件中使用 无链接对象指在块和函数体中声名的这一类对象,只能在他们的块中使用
------储存期:静态储存期指对象在程序执行期间一直存在,文件作用域(全局变量)对象具有静态储存期
块作用域的对象具有自动储存期,即当程序退出这个块是就释放这些变量占用的内存,自动储存期的对象被储存在栈中,如果对象所需内存过大,栈可能储存不下,可能出现错误
当在外层定义了一个变量x而又在内层定义一个新的x时,当程序运行到内层x处时外层的x会处于隐藏状态(即内层x暂时覆盖外层x),直到内层x消失
-----内存:程序的可用内存分为三大块,一块用于存储静态变量,一块用于存储自动储存期变量,一块用于存储动态分配的变量
堆:由程序员分配和释放。如在C/C++中程序员使用malloc/new分配堆空间,使用free/delete释放所申请的堆空间。特点:释放内存块顺序随意。
栈:栈是由系统自动分配和回收的内存。如一个子函数被调用时,系统会将函数内的局部变量的内存单元分配到栈上,当函数执行完毕时系统自动释放所分配的栈地址单元。
特点:释放栈内存顺序为后进先出。
使用递归时,就是一个明显的栈的例子,最后调用的函数最先执行完成,执行完最后调用的函数后会回到调用这个函数的入口继续往下执行,直到执行完

------用于优化程序运行的标识符-------
volatile 假如一个变量被多次赋值给其他变量,而不改变它的值,则可以用此标识符 例:int a;int b=a;int c=a; 则可以用此标识符修饰a
restrict 假如某一对象只使用指针(且只使用这一个指针)访问它,则可以给这个指针本身加此标识符 例 int* restrict p=(int*)malloc(20*sizeof(int));

进制间的转换

二进制最前面的0可以忽略从1开始算
–十进制转换为2进制:例58 用除法(除2),偶数则为0,奇数则为1并减1继续除2,结果从下往上排列就是二进制
58-0-29 29-1-14 14-0-7 7-1-3 3-1-1 1 (除到为1时,1模2等于1)把结果从右往左排列就是二进制结果了 111010
–二进制转换为十进制:假如二进制数一共有n位,那么第一个二进制数就按n-1位算(一般只用算1就行了,因为0的结果是0)
1 * 2的六次方+0 * 2的五次方+1 * 2的四次方+1 * 2的三次方+0 * 2的二次方+1 * 2的一次方+0 * 2的零次方(1011010)
–二进制转为八进制:从右边开始取三位二进制数,不足三位用0补 02的0次方+12的1次方+02的2次方=2(其余结果为3和1)结果为132 (1011010)
–八进制转为二进制:把八进制的每个数按十进制转为二进制,每个数的结果不足三位数的补个0,最后按顺序排列 (1—001 3—011 2—010 结果为1011010)
–二进制转为十六进制:算法与八进制相同,但是是取四位算 0~9 A-10 B-11 C-12 D-13 E-14 F-15
–十六进制转为二进制:算法与八进制相同,但结果取四个二进制
八进制和十六进制和二进制转为十进制方法一样,例八进制数213等于十进制数3
8的0次方+38+38*8

位操作

//在vs中,字符0等于整数48,0~9依次递增 字符a=97 a~z递增
字节字符和位的关系 位是计算机存储的最小单位,一位表示数字0或1 在大多数计算机中一个字节包含八位 字节和位是存储单元
而一个字符等于多少字节与字符集和使用的语言有关(字符是数据类型)
ASCII码:一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间
UTF-8编码:一个英文字符等于一个字节,一个中文(含繁体)等于三个字节。中文标点占三个字节,英文标点占一个字节。
Unicode编码:一个英文等于两个字节,一个中文(含繁体)等于两个字节
如何用二进制表示负数? 即数据类型为有符号类型一般一个字节(即八位)能存储的最大数为11111111(255),无符号的范围为0255(256个数),有符号为-127128
有符号数11111111(255)表示的值为-1,10000000(128)为-128,所以在128到255范围内的值为负的,0到127为正数,即00000001为1
将一个二进制数的每一个0换成1或每一个1换成0其结果再加1则取到相反数 例:00000001等于1则11111111(11111110+1)等于-1
位运算符 只用于处理二进制数(用于处理其他进制的数会将其转为二进制处理然后再转回来)
用法:可以利用位运算符修改某二进制中的0和1(假如0是关1是开,则)运算符可以一起结合使用
1.~ 取反操作符 ~(11001)返回的结果是00110 作用:将开的都关上,关的都打开
2.& 与操作符,依次比较每个位,只有当其都为1时,才为1(两边都是真才返回真)将1与0=0
例:(10010)&(10011)返回10010 作用:两边对应的开关如果都是开则保持打开,其余的都关上
3.| 或操作符(有一个为真则返回真) 将1与0=1
例:(10010)|(10011)返回10011 作用:两边对应的开关如果都是关的则保持关闭,其余的都打开
取一个数的某些指定位可用与操作符,
4.^ 异或操作符(只能是其中一个为真则返回真)1与0=1 其他都为0(同号为0)
例:(10010)|(10011)返回00001 作用:两边对应的开关如果有一个开一个关则使关的打开.其余的关闭
应用:使一串二进制数翻转(0-1,1-0).2.交换两个值,不用临时变量。a=a^ b ;b=b^ a ; a=a^b;
5.“>>” 右移运算符,将1向右移,空白用0补 例:(10010)>>3结果为10000
6.<< 左移运算符将1向左移 用法:numb<<3 结果为numb乘以2的n次幂(>>为除)
如果两个数据长度不同(例如 short和int型)进行位运算时(如a&b,而a 为 short,b为int型),系统会将二者按右端对齐。如果a为正数,则左侧16位补满0;若a为负数,左端应补满1;如果a为无符号整数型,则左侧添满0。
位字段 通过结果声名和建立 _Bool类型也是位字段 位字段存储二进制数
struct{unsigned int a:1;unsigned int b:2;signed int c:1;}color; 一个叫p的结构,包含三个位字段abc,a是无符号位字段,存储1个位(0或1)b存储2个位
如果赋予的值超过可存储大小则存储最后几位,例color.a=9; 十进制9等于二进制1001,所以color.a为1 color.b=10; 10等于二进制数1010,则color.b为10

头文件操作

宏定义:一般用宏代替简短的表达式
#define 标识符 字符串 //即用标识符表示这个字符串,
#define定义的常量和函数本质是做代码的转换 ,例:#define f(x) xm #define m n+1 则f(1+1)=1+1n+1=n+2 (x表示字符串1+1 m表示字符串n+1 所以f(x)表示字符串1+1n+1)
2. #define char byte 则可以用byte代替char声名变量,但一次只能声名一个
3. #define sout(x) printf(“this is”#x"or is %d",x+x) sout(3+2); 则输出 this is 3+2 or is 10 即"#x"输出为传入的参数,如果参数是y
y,则"#x"为y*y
4. #define sout(x) printf(“this is”#x"or is %d",s##x) sout(3);则输出 this is 3 or is is s3
5. 变参宏 … 表示多个参数,对应_ VA_ARGS _
例:#define ct(x,…) pfintf(“sd”#x"d"_ VA_ARGS ); ct(1,“a=%d,b=%d”,a,b); 输出 sd 1 da= a ,b= b
文件预处理:#include #include"int/ctype.h" 两种方法都用于查找文件,前者表示在标准系统目录中查找文件,而后者则先在当前目录找,当前目录没用则在标准系统目录中查找文件
可以用#include指令引用其他文件的代码。这行代码表示用a.c中的所有代码取代这条指令
进行编译时则预处理(执行)a.c中的所有代码
条件编译 可以用于main函数中
#ifdef mc //如果已经用#define定义了mc则执行下面的代码。#ifndef :如果mc未定义过则执行下面的代码,其他用法不变
#define cc 20
#else 否则执行下面的代码
#define bb 20
#endif //if结束
—#if和#elif #if后面可以用常量不等式例:#if mc ==20 #elif和else一样,语句依然用 #endif结束
#if 后面的表达式值为非0,则往下执行
其他指令
—#undef 用于终止宏定义的作用域,即宏定义的标识符的使用范围到#undef结束
例:#define mc 200 #undef mc 则mc相当于没用定义过
—#line 用于设置
FILE LINE _ 例:#line 10 “123.c” 将行号重置为10,将文件名重置为123.c
—#error 将时编译中断并发送一条信息 例#error game over 则输出game over和一些基本错误文本

iso646.h 可以用and or not分别代替&& || !
ctype.h 包含一些用于测试字符的函数 如:isalnum(a) 判断a是否为字母或数字
stdio.h I/O包 包含标准输出和输入(stdin和stdout,stderr)
string.h 包含许多处理字符串的函数(如:strlen(),strcat()等等)
stdlib.h 包含将字符串中数字转为int或者double等等类型 malloc()和free() exit();
math.h 包含用于数学运算的函数

-----其他预定义宏,就是和EOF一样是预定义的值
_ DATE _ 当前时间 例:Nov 23 2013
_ FILE _ 表示当前源代码文件名 例:123.c
_ LINE _ 表示当前源代码文件中行号的整型常量(即当前行是第几行)
_ TIME _ 执行到当前代码处的时间,格式为“hh:mm:ss"
_ STDC VERSION _ 支持C99标准,则其值为199901L;支持C11 标准,则其值为为201112L

转义字符

—printf(“sda\
bat\n”); //即等于printf(“sdabat\n”)
用一个空格字符代替一条注释
\r:将光标移到这一行的开始
\b:光标前移一格
\n:光标移到下一行起始
\ 打印
’ 打印’
" 打印"
? 打印?
%% 打印%
%用于打印某些特定数据类型如%d打印int类型 在%后面加如下字符会有特殊含义(即在%和d之间加下面这些字符)(用于printf函数中)
”-“ 左对齐打印,从左侧开始打印 如%-4d(假如待打印的int值为3) 则输出结果为3 (后面有三个空格)
”+“ 取反 如%+d(假如待打印的int值为3)则输出结果为-3
空格 若值为正则在值前面输出一个空格,若为负则输出原值(空格被-替代)
“#” 如果是%#o则先输出一个0再输出结果 如果是%#x(或X)则先输出0x(0X)
0 代替空格
数字 3.2表示最少输出3个字节且输出小数点后2位
”* “ 与数字含义相同,但需要指定数字,在待打印里面指定数字 例:printf(“% * . * f”,b,a,c)第一个 * 是b第二个*是a . 打印c

函数

-----函数return在递归时的实践经验-------
递归中的return,return会返回一个值到寄存器中,并结束当前函数,回到调用这个函数的地方,
假设在函数f中调用了f,当程序进入f中的f,并返回一个值时,则里面的函数f会结束(在函数调用的地方结束,但外层函数并不会结束,会继续往下运行)
例:int f(int a){
if(a<3){
a++;
f(a); //在进入这个f时,如果在里面使用return,则会返回到f(a);分号后面继续运行,即执行完了f(a);这一条语句,
printf(“内层f结束”);
}else{
return a; //当输入a的值为1时,函数运行结果为:内层f结束 结束 内层f结束 结束 返回值为7(为什么返回7???内层函数使用return返回一个值到寄存器中,但这个值没有被引用,而当外层函数也返回一个值到寄存器时,会发生什么?函数最后返回的值是什么)
}
printf(“结束”); //内层函数return后会返回外一层继续运行到 } ,则函数结束继续返回外一层,直到最后一层
}
上面的程序运行顺序为,return a(a=3) printf(“内层f结束”);printf(“结束”);(a=2) printf(“内层f结束”);printf(“结束”);(a=1) 整个f函数调用结束,返回到调用f函数的地方

//函数的返回类型不能是数组,但可以是指针类型
例:void a(int x){x=3;} main(){int x=0;a(x);} 程序运行结束后x的值依然为0,因为在a函数中使用的x是在main中定义的x的复制品 可以在定义a时将参数改为 (int & x),这样x的值就会改变
//在自定义函数中无法通过sizeof 获得数组的大小(在定义的参数为没有指定大小的数组时,例 void a(int b[]){int c=sizeof(b);} 则不管在调用a时传递多大的数组,c的值都为4(因为b是一个int类型指针)
//!!!!!!!!!return在函数递归中的使用,若使用return在递归中调用自己,则要设置出口,(最后一层的出口)
在使用递归时,如果要return一个值,最好不要使这个函数过于复杂(即不要在函数内多次调用这个函数),因为在return只会使内层函数结束
解决方案:创建一个变量用于接受内层函数返回的值,在内层函数调用的地方以下的代码用if语句括起来 这样当内层函数返回一个值时,p的值就不是初始值了,就不会执行下面的代码,然后直接返回p就行
例:假如创建用于接受的变量为p(p创建于函数内部),将其初始化为0(这个值不能是可能返回的值),则判断为if(p==0){内层函数调用的地方以下的代码}else{return p;}
//可以定义同名函数,例:void a(){printf(“456456\n”);} void a(int b) {printf(“%d\n”,b);} 在调用时,调用a(11)就是调用的需要传参数的名叫a的函数,但如果是带返回值的函数,就会报错

如何自定义一个函数?:例:double count(int a,int b){} 定义一个叫count的函数,返回类型为double,需要传入两个int类型的参数
定义函数可以省略后面的参数名, 上面的例子和double count(int b[] ,int){} 等价(int b[]表示传入一个指针,仅用于函数的定义或者声名中,和intd等价)
函数名字是函数的地址
------memset memset(dp, 0, sizeof(dp));//将数组dp中的所有位置的值设置为0
------typedef char byte 则可以用byte代替char声名变量,
------const 一个标识符,用于表示某数据类型为只读(即其值不变)const int
const p; 则指针p的值和其指向的地址不变
------static 一个标识符,作用1:使被标识的对象成为内部链接的对象 2:使被标识的对象具有静态储存期
3:int count(int a[static 10]); 表示该函数传入的参数是一个数组(指向数组首元素的指针),其大小不小于10
不能在函数()里面使用static,循环里面可以用 局部静态变量指具有块作用域和静态储存期变量
------register 一个标识符,用于声名变量是一个寄存器变量,无法获取寄存器的地址,因为寄存器变量是储存在cpu的寄存器中
------auto 一个标识符,用于声名变量是一个局部变量(自动存储器/自动变量),局部变量和寄存器变量只有在使用时在占用内存空间
------extern 一个标识符,声名该变量是外部链接变量(即引用外部变量),用此标识符不会为变量额外开辟内存(如果使用在另一个文件定义的变量,则必须加上extern标识符)
------malloc()和free() p=(int*)malloc(20sizeof(int));free§;函数malloc()用于分配一块内存,这行代码利用malloc()分配一块可以储存20个int类型数的内存,
malloc()默认返回一个void类型的指针,void类型可以强转为任意类型,该指针指向malloc()分配出来的内存中的第一个元素(可以把这行代码看做是定义了一个数组,可以用处理数组的形式处理)
(即p[i]表示第i个数) free用于释放malloc()划分的内存 malloc()可能分配不到所需的内存,但出现这种情况是malloc()会返回一个空指针(可以加个判断当p==null时结束程序)
创建一个二维数组:p=((int
)[20])malloc(n20sizeof(int)); 一个n*20的二维数组
struct book *p=(struct book *)malloc(sizeof(struct book)+sizeof(int)*5) 为p分配空间,其大小是struct book的大小加上5个int类型的大小
//对同一对象,每次分配的内存的地址不同,也就是说如果你在p中存储了数据,然后执行p=(struct book )malloc(sizeof(struct book)+sizeof(int)5) 则这时候的p指向的是新分配的内存的地址,
//若使用malloc分配了一段连续的空间,在返回指针时,是根据你给予的数据类型将这片空间分割的(分配空间是按位分配,按数组下标访问时是将空间按每4(int)位分配),
例如返回的是int指针,则是每4(sizeof(int))位一个数,若返回的是long,则就不是每四位一个数了,访问时就会出错
------calloc() 和malloc()用法一样calloc(100,sizeof(long)),不同处在于calloc()需要传入两个参数。第一个参数是储存单元数量,第二个是每个储存单元的大小
------exit() exit()函数用于结束当前程序 exit(EXIT_SUCCESS);表示程序正常停止 exit(EXIT_FAILURE);表示程序异常停止
------atexit() 参数是函数地址,即注册函数,最多可以注册32个,当调用exit()时,会执行已经注册了的函数 被注册的函数必须是无参且无返回值的 此函数必须写在exit() 前面
------_Alignof() size_t b= _Alignof(float) _Alignof()参数是一个数据类型,返回这个数据类型占用的字节
_Alignas() char _Alignas(int) b; 将char类型的b按int类型的大小存储
------泛型选择_Generic() 传入任意类型的参数,根据参数的数据类型返回指定的值
例:_Generic(x, int :“123”,double : 4,defaule: 3.2); 则:_Generic(3)==“123” _Generic(3.2)==4 _Generic(“123”)==3.2 如果没用定义和参数匹配的数据类型则返回defaule后面的值
------inline内联函数 一个标识符,用于标识函数,声名该函数为内联函数 一般在inline后面加static标识符将函数声名为内部链接,因为标准规定内联函数应具有内部链接
因为程序调用普通函数会跳转到函数定义的地方并返回,这样会造成开销,将其定义为内联函数者没用这样的开销,程序在调用内联函数时会相当于运行内联函数内定义的代码,
不用跳到内联函数定义的地方去。较长的内联函数并未节约多少时间,内联函数因为具有内部链接,在多文件调用内联函数时可以将内联函数定义在头文件中
例:inline static int count(int x){return x
x;} count(3); 调用count(3);时相当于执行 return 3
3;(相当于在函数调用的位置输入函数体中的代码)
------assert() 参数是一个返回真或假的表达式,如果为真,则什么都不输出,如果为假就终止程序并利用标准错误输出流输出一条语句,并标明assert()所在的行数
好处在于这个表达式可以用一行语句令程序中的所有assert()失效,即#define NDEBUG
------_Static_assert() _Static_assert()是在编译前就做判断,即先检测传入的表达式是否为真,然后再开始运行程序
_Static_assert()需要传入两个参数,第一个是整型常量表达式,第二个是字符串,如果为假就输出这个字符串
-------- ------可变参数
定义可变参数有两种方法,1,利用#define预定义可变参数(见头文件)
2.利用stdarg.h提供的宏定义和用可变参数(较复杂)
步骤1:int count(int a,…){ 最少传入一个形参,即除…外还应有一个参数,而且…只能放在最后,离…最近的参数必须为int类型,表示…中参数的数量
va_list aa; va_list是stdarg.h提供的宏,用于存储…中的参数
va_start (aa,a); va_start用于初始化va_list,第二个参数是aa中参数的个数(即a)
long l=va_arg(aa,long) va_arg用于返回va_list中的参数,第一次调用va_arg则返回第一个参数,第二次调用则返回第二个参数,以此类推,第二个参数是…中参数的类型(第一次调用即…中第一个参数的类型)
int i=va_arg(aa,int)
va_end(aa); 释放资源,调用这行代码后aa将被删除,需要重新用va_start初始化才可以继续用
ca_copy(va_list bb,aa) 将第二个参数拷贝给第一个参数
}
------随机数的生成:c语言中有rand()函数随机生成一个数,rand函数是以srand()函数通过一个种子经过运算获得的随机数,这个种子在计算机每次运行时不同,
直接使用rand可以获得一组随机数,但下次获得时还是这组数,因为种子没有变,所以值没有变,所以要使用srand函数来循环获得随机数
我们可以利用sand(unsigned int seed)自己定义种子,可以利用时间函数每次运行时产生不同的种子
例:srand((unsigned)time(NULL));
然后可以对产生的随机数进行一些运算获得一些范围内的随机数
例:int a=rand()%10; a是0~9内的随机数
srand((unsigned)time(NULL) + (unsigned)rand());但需要生成多个随机数时,如果将srand放在循环内,因为循环执行的速度太快,种子没有改变,会出现相同的值
常用数学函数:pow(2,3) 返回2的3次方,返回double类型的 pow(10,2) =1000

运算符

//a||b++ 判断前面等于真时不会执行后面的语句,即当a为真时,不会执行b++
----优先级:上到下,左到右
[] () . ->
! + - * & 指针,一元运算符

  • / %求余 整数相除结果是整数(取整),浮点数相除的结果是浮点数 7./2结果是3.5 5%3结果是2
  • -加减法
    << >> ^ == != && || ? 位运算和逻辑运算 (一个&是位运算符)
    = += -= 赋值运算 ,逗号(,号运算符的结果是最后一个表达式的值(按左右顺序运算))

b++相当于b=b+1 (先执行其他语句再执行b+1) ++b相当于b=b+1(先执行b+1再执行其他语句)
b+=c 相当于b=b+c(其他如-/等类似)
------逻辑运算符 && 与(一假则假,全真为真) ||或(一真为真全假为假,) !非
条件运算符 ? x=(c<0) ? a : b 如果c<0,则x=a,否则x=b 这一点和Java相同
递增或递减运算符一般不要用于一个变量多次出现的情况,因为编译器执行的顺序是不确定的(如n=3;y=n+++n++;程序可能先执行第一个n++,使n递增,结果y可能是7)
a[i] 等价于
(a+i) a是一个指针

循环语句

//for()if()a=b; 这也是个完整的循环
实践经验:在循环中使用递归(即f在循环中调用),当进入递归内,(即在函数f里面调用函数f),在内层函数f里运行return返回值时会出现错误
(错误1:不会返回值,也不会使外层f结束,错误2:外层循坏会继续执行,错误3:出现混乱)
//在函数中使用递归就有可能会使return结束不了函数,使函数无限递归
例:int f(int a){
for(int i=0;i<5;i++){
if (a<5){a++;f(a);} //在循环中调用f
else{return a;} //但a=5时,函数会返回5,但实际却没有,而是在执行到这一步时返回外层循坏,继续运行循环,而且a的值会出现莫名的-1,然后在某一个时刻终止函数并返回a,且a=5
} }
------while与Java用法相同
------for的用法 例:for(int a=1;a<2;a++)printf(“%d”,a); 与Java用法不同,没有花括号表示循坏体,而是把整个循坏写成一行代码,以;结尾
可以省略for()里面一个或多个表达式但不能省略; 如for(inta=1;a<2;)a++,printf(“%d”,a);
每一个;里面可以写多个表达式,用,隔开
如果没有花括号,else和最近的if匹配(用Java的写法也可以)
------do-while和Java用法相同 例:do{a++}while(a<0);
------switch-case循坏:用法和Java相同 例:switch(int a){case 1:a++;break; case 2:a–;break;default:a++;} 当s等于1时,a递增1,当a等于2时,a递减1
假如上面没有break,则当a=1时,程序会从case 1:这一行开始执行到switch语句结束(case后面的值和a的值相等的那一行开始),
则上面语句执行了a++和a–(即不判断后面case后的数值是否和a相等,都执行case后面的语句)
假如a的值和每一个case后面的值都不相等,则执行default后面的语句
多重检测switch语句:即在一个case前面再加一个或多个case,但多加的case不含break,则程序会直接往下执行直到遇到break
例:case 1:case 2:a++;break; 则当a等于1时,会执行a++,然后循坏结束,当a等于2时也会执行a++,然后循坏结束
------goto语句:一般不使用这个语句 例:goto a;a:b++; 则程序运行到goto a;时,会运行a指向的代码b++
continue:一般用在循环体中,程序运行到continue时结束当前轮循坏(只会影响包含continue的循坏)继续开始下一轮循坏
break:结束当前循坏(只会影响包含break的循坏)

基本数据类型

(只谈与Java不同)
//char强转int转为ASCII码
//int类型自动向下取整(直接截断小数点后面的数)
unsigned 声名该类型是无符号整型,只能表示0和正整数
size_t 表示无符号整型(包含所有无符号整型,系统会自动匹配用unsigned int 或unsigned long等等) size_t的大小可以保证足够存储内存中对象的大小
_Bool——布尔值false(0)和true(1) 也是一种整数类型 (在c语言的判断中,所有的非0值都是真)
_Complex——复数
_Imaginary——虚数
%p打印所给值的十六进制形式
%d打印int类型 %ld打印long lld打印long long
-cha类型 声名并赋值–char b=‘a’; char b = ‘abcd’;(则b为d,因为超过可存储的大小最多只能存储一个字符输入ab则b等于b)
%c 打印char类型
%s 打印char数组 等于字符串
%lf 打印double(默认打印后六位)(%le也是double) %2.1f表示打印小数点后1位数最少打印2个字节(312.142打印为312.1)%10.2(312.142打印为 312.14(前面打印4个空格))
%g打印double(小数点后有几位数就打印几位数,最多打印四位数)
%f打印float float最少在小数点后有六位时使用
0x和0X前缀表示十六进制(如0x10表示十进制数16,10为十六进制数)
%x表示以十六进制显示数字(即把待打印的数转换为十六进制数并输出)字母是小写
%#x表示显示十六进制前缀(0x),%#X表示显示十六进制前缀(0X)
0前缀表示八进制 %o表示以十六进制显示数字 %#o表示显示十六进制前缀(0)
在给变量赋值时,一般会升级,只有char和short会转换成int处理,float会转换成double处理这一点和Java相同,处理方法也和Java相同,可以使用类型强转(用括号)
------枚举:enum p={red,yellow,blue}; enum p pp; enum相当于int类型,在能用int的地方都可以用枚举,p相当于一个int类型的数组
而pp则是p数组里的某一个成员,如:pp=red;pp++; 则pp等于yellow pp=yellow;pp++;则pp等于blue
一般枚举的值默认是从0开始递增,即red=0,blue=2 也可以给枚举赋值例:enum p={red,yellow=20,blue}; 则red=0,yellow=20,blue=21

字符串输入与输出函数

c没有专门处理字符串的函数,但可以用char数组处理字符串 如: char count[40]; 定义一个名为count的字节数组,其大小为40字节
其他未使用的空间也都为\0 实际只能存储39字节,因为还会存在一个\0(空字符,不打印,在数组末尾,标记字符串的结束)
一整个字符串有一个地址,他的地址为其首字母的地址,所以该地址的值为其首字母的值 例:“s1a”的值为s,&“s1a”为“s1a”的地址
char p=“wang123”; 这行代码和char s[]=“wang123”;基本相同,因为s本身表示这个数组的地址
打印字符串时,打印字符串的第n项地址,表示从字符串的第n项开始打印(包括第n项)
------puts(“123wang”); 打印123wang并自动换行(puts函数只用于处理字符串,并会自动在字符串末尾加上换行符)
puts();里面可以传入一个指向字符串的指针, puts()会在遇到空字符时停止(\0)
------char shuru[]; gets(shuru); 获取键盘输入的字符串并赋值给shuru,在遇到换行符时结束,并不会把换行符赋值给shuru
------gets_s() 和fgets()不同的是他不需要第三个参数,直接从键盘获取输入,会将换行符读取并舍弃,
如果输入的数超过gets_s()可以读取的数,会出现问题(不要超过!)
------printf(“”,a,b) 双引号内是打印的内容,a和b是待打印的内容
打印时,如果这一行打印了某些内容,而光标又移到这一行开始,则后续打印会覆盖已经打印了的内容
何时会将打印内容输出到屏幕?printf会将 在遇到换行,需要输入,或者缓冲区满时会输出到屏幕
------sprintf(s,“ok:%d”,b); 用法基本和printf相同,但不打印内容,多传一个参数(一个char类型的指针),把第二个参数里打印的内容赋值给第一个参数
相当于把printf()要打印的内容赋值给一个传入的char类型的指针(即s)
------scanf函数用于控制台输入
scanf(“%f”,&b) ——第一个引号里面填需要输入的数据类型,后面的&后写需要赋值的对象(将输入的数据赋值给b) 字符数组不需要&
scanf打印时在遇到空格,制表符或换行符就会停止获取 而且scanf在vs里面不能直接使用,因为scanf在读取时不检查边bai界,所du以可能会造成内存泄露
在vs中使用scanf需要在程序最前面加#pragma warning(disable:4996) 以抑制警告
a=scanf(“%c%c”,&d,&b); scanf会返回一个int值,表示读取的项数(a=2),或者返回EOF
scanf在打印时会依次匹配待打印的数据类型(在遇到换行,制表符和空格会结束一个匹配然后跳过换行,制表符和空格开始下一个匹配)只有%c(字符)会读取空格
scanf(“%c”,&d); 会从第一个字符开始读取,包括空格(读取其他数据类型时都会忽略空格) scanf(" %s",&d); 从第一个非空白字符开始读取
%d(放在中间表示跳过前两项)这里表示跳过两个整数,读取第三个整数
scanf(“%10s”,&d); 表示读取10个字符或者遇到读到第一个空白字符
------putchar()和getchar()他们只处理字符 char ch; ch=getchar(); 获取下一个字符并将其赋值给ch getchar(ch); 打印ch
putchar()和getchar()和scanf()在读取文件或者输入时读到末尾会返回一个值EOF,而当输入Ctral+Z时会被视为结束,在UNIX系统中Ctral+D用于标记文件结束
putchar()和getchar()在读到末尾返回0~127 scanf()返回0~255
//strlen()函数,如果传入的参数字符串是这样定义的char c[5] = { ‘9’,‘6’,‘7’ },则返回的是实际大小,即3 若是用malloc定义的字符串,默认没有\0,结果不准确
------strlen()函数 返回字符串的长度 打印用%zd(sizeof也一样)(实际大小) 从数组开始读到\0结束
不包括\0
------sizeof()函数 返回字符串的大小其类型是size_t 在使用char[40]数组时,用此函数得出的结果是char数组的容量大小(40
4)字节(占用内存大小)
而在处理define字符串大小时会把末尾空字符计算在内,所以得出的结果会比实际大小大1 如果读取成功则返回1失败返回0
------strcat(“ab”,“cd”); strcat()函数将传入的两个字符串拼接起来,其结果赋予第一个参数并返回第一个参数的地址,第二个参数不变(等于说复制第二个字符串的内容到第一个字符串后面)
strncat和strcat类似:strncat可以传入第三个参数表示第一个参数拼接的字符数,如strcat(“ab”,“cd”,1);结果返回“abc”
------strcmp(“abc”,“ss”); 比较传入的两个字符串是否相同,相同返回0,否则返回非0(内容是否相等)
strncmp(“abc”,“ss”,n); 比较前n个字符
------strcpy和strncpy,拷贝字符串 char bb[10]=“12” aa=strcpy(bb+n,“wang”)返回值aa是一个char类型的指针,表示的字符串和第二个参数相同,即char
aa=“wang”地址不同
strcpy函数从bb的第n项(包括第n项)开始写入第二个参数(并覆盖原有的数),并且会将第二个参数末尾的\0写入,所以假如n=2,则bb等于“12wang\089”
strncpy用法:第三个参数表示要复制的字符串过来的字符串个数 //若是拷贝过来的字符串大小大于bb的容量,会覆盖bb中的\0,在打印时会出现乱码
------atoi(); 传入字符串,将里面的数字转换为int类型并返回 atol-long atof-double
strtol和strtoul是atoi的升级版,会报告传入的字符串第一个数是否是数字,分别返回long和unsigned long strtod-double(默认以十进制打印,所以只需要两个参数)
long q=strtol(char
a,char**b,int c)第一个参数是待转换的字符串,第二个参数是一个指针的地址,也可以写为&char b,用于接受读到末尾时的字符的地址。第三个参数是指定的进制,最大输入32
假如输入char
a=“10”;c=16则q为16(10被转为十六进制数并赋值给q),b为\0(打印显示空)输入char
a=“15ab”;c=10; c=16则q为15,b为ab,

数组

//如何对数组进行动态扩容?1.只能用malloc函数重新分配一块更大的空间,然后将原数组的值拷贝到新分配的内存中
2.如何在程序运行时监测某一变量的变化?即当数组中存储的数接近容量大小时对数组进行扩容
//实践:当一个指针数组进行这样的操作时:char b;chara=b;则a的值会随着b的值改变而改变,如果想让a的值不随着b的值改变而改变可以先给a用malloc分配内存,
这样a就指向了某一块内存,然后利用memcpy将b的值拷贝给a
—在对数组用= =进行比较时,只要内容相等,则返回真
-------定义一个数组 1:int a[5]; 必须给出数组的大小
2:charb; 字符串指针,等同于数组 3:p=(int)malloc(20sizeof(int));分配内存,等同于创建一个数组
如果需要用到数组而数组名字不重要,就可以省略名字 例:int
p=(int [2]){2,3};? p是一个含两个元素的数组的指针,相当于p就是这个数组的名字,p=2 p[1]=3(一个值) p=p[0](指针)
数组名称是其首字母的地址,例:arry==&arry[0];
如果没有定义数组的大小,则自动匹配大小(数组内有几个值,数组大小就是几)
数组只能在初始化时可以用花括号赋值,像arry1=arry2这样的赋值也是不允许的
int arry1[4]={[3]=212}; 指定初始化某个值,未初始化的值默认为0
int arry2[4]={10,[3]=20,30,[0]=40};(运行到[3],则指针跳到第四个位置,令其值为20(覆盖原值)) 结果为 40 0 0 20 30
------多维数组–
int array[3][6]; 3个数组,每个数组包含6个数
int array[2][3]={2,5,6,7};(从第一个数组的第一个位置开始赋值,结果为2 5 6 7 0 0)
多维数组指针:array代表首个数的地址,他的首个数是array[0],而array[0]是一个一维数组,他代表他的首个数的地址即:array[0][0]
所以
array的值是array[0]的地址,而**array则相当于array[0]则等于array[0][0]的值,array指向array[0],所以array+n等于array[n]也就等于array[n][0]的地址
------对数组排序 void qsort([],size_t,size_t,int(cc)(const voida,const void
b))函数 该函数对传入的数组进行排序
排序原理(将一个数组分为两个部分进行比较,无限分下去,直到最后)
初步实践qsort不好用,建议只用于简单数组的排序
第一个参数是需要排序的数组的地址,第二个参数是数组的大小,第三个数组是数组中每个元素的大小(例sizeof(int))
第四个是自己定义的比较方法(一个自己定义的函数,返回int,参数是两个const voidb,即两个void类型的指针)
对于自己定义的比较方法,一般步骤是1:将两个参数转为与带排序数组类型一样的指针例:const int
aa=(const int*)a; const int* bb=(const int*)b;
2.if(*aa<*bb){
return -1;
}else if(*aa==bb){
return 0;
}else{
return 1;
}
------拷贝数组 memcpy() memmove()和memcpy()类似
memcpy([a],[b],n
sizeof(int)) 第一个参数是数组的下标,第二个参数是数组的下标,第三个参数是需要拷贝的元素的个数
即将第二个数组第b个元素后面的n个元素拷贝到第一个数组的第a个后面,

结构

(相当于Java中的图)和联合
//结构中可以定义方法,但不能调用方法
//结构必须初始化后才能访问结构中定义的成员,(不能在定义结构的地方初始化结构,因为定义结构不会分配内存,所以初始化没用)
结构名不是地址,结构对象名也不是地址(book不是地址)。但结构数组名是地址(books是地址,和数组一样)
创建一个结构: struct book{char a[];int b;double c;}; 结构名为book,可以储存三种类型的值 可以把struct book看做是一种自己定义的数据类型
初始化结构(分配内存):struct book Cpp={‘a’,2,2.0}; 将Cpp定义为book类型的结构,并分别赋予三个值
访问结构成员 Cpp.a 即等于在Cpp中定义的数组,(Cpp.a相当于一个数组)
------结构数组 struct book books[10]; books是一个含10个book类型元素的数组
------结构指针 struct bookp; 创建结构指针并没有像创建数组一样创建一个结构,结构指针类似于int指针,用于指向地址
p=books; 现在p是指向books p=&book; p指向book的地址,因为p不是结构(p==&book),所以p不能使用p.a,但
p可以这样用,因为p==book(&抵消了)
因为.的优先级是最高的,所以要写成(*p).a 还可以写成p->a(相当于(*p).a)
------可变数组 可以在结构中定义一个可变数组,但有限制:1.这个结构除可变数组外还要有一个成员,2.这个可变数组必须定义在结构最后一个3.可变数组的方括号里面不含参数
例:struct book{double c;char a[]}; 数组方括号里面没有参数则系统没有为此数组分配内存,则无法访问和初始化这个数组,必须要用malloc()分配空间后方可使用
带可变数组的结构不能进行赋值和拷贝(结构和结构),但可以用memcpy()函数进行拷贝
实践:在使用可变数组时,如果要改变传入的数组的值,只能定义为数组指针,如果是char a[],则在给a赋值时只能用“4648”赋值(最好不要用char a[],在打印a时会有错误(vs))
—联合,联合的用法和struct基本相同,但结构只能按顺序赋值和访问,而联合可以是无顺序 union book{int b;double c;};
联合体占用的内存空间总是等于当中最大的一个数据类型的大小 联合中的所有元素共用一个空间地址(即联合中所有元素的地址是一样的)(起始地址是一样的)
改变联合中的某一值,则联合中的其他值也会改变(因为联合中的所有变量的地址是一样的。所以改变一个变量的值相当于改变所有变量的值)

IO

//写入到文件基本都是覆盖式写入,现在指针在2位置,写入3个字符会覆盖原来在第2到第5位置的数据
//总结:2.输入到文件打印文件内容,3.打印到控制台
2.getc()和putc()用于处理单个字符 fread和fwrite(数组,sizeof(char),int a,file)二进制
23. fputs(“ddd”,stdout) fgets(a,b,stdin);换行停止或读b-1个字符 fscanf(stdin, “%d”, &i)遇空格停止 fprintf(file/stdout,“”)
//在循环中使用scanf从控制台获取字符时不要用%c,会出问题(用%s)
—feof(file)和ferror 用于判断是否读到了文件末尾。在io中读取到末尾会返回EOF,然而在读取错误时也会返回EOF,feof在上一次输入调用时检测,若到文件末尾则返回一个非0值,否则返回0。
一般这样使用 while(!feof(file)){//在这里写io语句,若执行到文件末尾会退出循环}
ferror在程序出现读写错误时返回非0值,否则返回0
------stdin和stdout,stderr 这些是文件指针,分别指向键盘,显示器和错误输出(即在程序出现错误时输出一些字符,一般在错误输出后面用exit结束程序))
EOF是一个已经在头文件stdio.h中定义了的常量,程序在读到文件(或字符串)末尾时会返回EOF,其值一般为-1
-----rewind§; 该函数让程序回到文件p的开始处
------FILEp=fopen(a,“r”); 打开名叫a的文件(可以是地址或者字符串),(下面是文本模式)在以下名称后面加b则用于以二进制模式打开文件(例a+b)
该函数会返回一个打开的文件的文件指针,
r:表示以只读模式打开文件 r+:读写模式
w:只写模式,该模式会将原文件的内容清空,如果文件不存在则创建一个新文件 w+
a:只写模式,该模式在源文件末尾开始输出。 如果文件不存在则创建一个新文件 a+
在以上名称后面加x则无法打开一个已经存在的文件,而且会以独占模式打开文件(即其他程序无法访问这个文件)
------getc§和putc(“rr”,p) 从文件获取一个字符和写入一个字符到文件
------if(fclose§!=0){printf("
***");} 在使用fopen函数后应该使用fclose函数关闭文件和刷新缓冲区,,如果成功关闭文件flcose会返回0,否则返回EOF
------fprintf和fscanf函数 第一个参数为文件指针,第二个参数是待打印字符覆盖式输入到文件 fprintf(p,“ddddd”); fscanf(stdin, “%d”, &i)
------fseek(file,10L,SEEK_SET); 定位程序在文件中的位置,此行代码定位到文件file的第10个字节处 第二个参数是long类型的值表示偏移量,最后一个参数表示从哪开始偏移
fseek函数的偏移量,fseek(fp,sizeof(int),SEEK_SET);光标在第一个int值后,(偏移量为1)
SEEK_SET SEEK_CUR SEEK_END 分别表示文件开头位置和当前位置和末尾位置 函数使用正常返回0,错误则返回-1(定位到一个不存在的位置)
------ftell(file) 返回文件当前位置距文件开始位置的字节数(long) 假如当前位置在第一个位置,则距离为0
------fread和fwrite 以二进制处理数据fread(数组,sizeof(char),int a,file)从file里面读取数据,一次读取一个char字节的数并存储到数组中,一共读取a个字节(fwrite用法相同)
fwrite一次读取一个int,共读取a次
—stevbuf 创建一个缓冲区供标准I\O函数替换使用
------fputs(“ddd”,stdout) fputs和puts一样,但不会在末尾打印换行符 fputs可以指定输出到文件,也可以用stdout指定输出到标准输出(控制台)
------fgets():用于替代gets,因为gets可能会造成缓冲区溢出(当输入的字符串的大小大于shuru可以存储的大小时就会溢出) 会将换行符读取并赋值给字符数组
fgets(a,b,stdin); fgets()可以传递三个参数,第一个是用于接收输入的内容(字符数组),第二个是输入的字符大小的最大值,即最多读取b-1个字符或者遇到换行符结束(int)
第三个是表示从哪里读入(可以是文件名,要从键盘获取用stdin)
返回获取的字符

指针

/*
指针a=b.next;
a=malloc; 则现在a不等于b.next,
解决方案和应用:在队列中,定义一个头指针f和尾指针r,入队尾指针往下走1,出队头指针往下走1
问题:如果r=f,则无法判断队列为空,如果r=f.next,则但r=malloc时,r!=f.next
解决:if(队列为空){
q->f->next = (Node*)malloc(sizeof(Node));
q->r = q->f->next; }
else{
q->r->data = x;
q->r->next=(Node*)malloc(sizeof(Node));//不直接对r分配空间,对r.next分配空间相当于对f.next.next分配空间
q->r = q->r->next;
}
/
//c语言中通过指针操作内存中某块地址中的数据,指针是一种数据类型,它的值是其在内存的地址,令指针a=指针b,则指针a等于b指向的的内存的地址。
//可以给未初始化的地址赋值,赋值成功,但会报错,比如定义A[6]; * (&A[5] + 0x1) = 3; //将A[5]后的地址的值设置为3,则A[6]的值为3,即A[6]的地址就是A[5]后一个int的地址
1.可以定义一个指针的值为A[6],则这个指针就是以A[6]为初始地址的数组 例:int
a = &A[6]; 则a[0]=A[6] a[1]=A[7] …
但这样写应该是不对的,在程序运行时应该会将数据存储在一个堆中,这个堆是有固定大小的,你不申请空间直接就对后面的地址赋值,当你赋值的地址不在堆空间内应该会出错,而你不知道这个堆空间的大小,所以这种操作是不对的
任何变量的地址是不能改变的, void p; 表面p是一个通用指针,即可以指向任何数据类型
a[i] 等价于 * (a+i) a是一个指针 int
a; int b[] a=b[1]; 则a[1]=b[2] a[3]=b[4],则a[1]=2的意思是 * (a+1)=2
指针是一串十六进制数,表示地址,指针自己也有自己的地址, 例:int * d ;int a=1;d =&a ; d的值是a的地址,d指向的值是a的值,&d是d的地址,&d!=&a
&a表示a的地址 %p打印地址 a=&b(把b的地址赋予a) * a的大小和b的值大小相等
间接运算符* a=*b(把b的值赋予a而两边的地址不变)
—声名指针 int b; b是一个指向int类型的指针。
int(p)[2]; p是一个指向含2个int元素的数组的指针 intp[2] 表示p是一个含两个指针元素的数组 []运算的优先级高于
运行[]表示是一个数组
------在数组中,地址加n,其地址便与数组的第n项的地址相同 例:&array[13]=&array[3]+10 地址加一则指向下一个元素的地址
在int数组中每一项的地址的十进制数值相差4 double数组中每一项差8 (一个数据类型占用多少字节则相差多少字节。
例:short占用两个字节,float占用4字节,long占用4(32)和8(64),unsigned long和long一样)
------指向函数的指针
void (p)(char); void是函数返回类型,p说明指针p指向该函数 char是函数的参数()
void tt (char *); void tpp (int); p=tt; 不能写p=tpp;,因为tpp参数不匹配 tt()不是地址 p(“fff”);相当于tt(“fff”); 也可以这样写(*p)(“fff”);

你可能感兴趣的:(C语言学习,c语言)