c语言是一种结构化的程序设计语言
支持三种结构:
1.顺序结构
2.选择结构:if switch
3.循环结构:for while do while
控制语句
1.条件判断语句
2.循环执行语句
3.转向语句:break continue break go to return
转义字符
\b将光标移动到下一个字符,但不是删除这个字符;
数据类型和变量
整型:char short int long long long (都有unsigned 和signed) int short long long long 就是signed ……对于char 是signed char 还是unsigned char取决于编译器
对于signed 最高位为符号位,0是正,1是负
浮点型:float double
构造类型:数组类型 结构体类型struct 枚举类型enum 联合类型union
指针类型:int*……
C语言的四种基本数据类型包括:整型、浮点型、字符型char和构造类型(如数组、结构体等)。
C语言中各种数据类型的取值范围如下:
- 有符号整型int:-2,147,483,648 到 2,147,483,647
- 无符号整型uint:0 到 4,294,967,295
- 有符号字符型char:-128 到 127
- 无符号字符型uchar:0 到 255
- 浮点型float:大约是 1.2x10^-38 到 3.4x10^38
- 双精度浮点型double:大约是 2.3x10^-308 到 1.7x10^308。
关于char
类型,在C语言中,它既可以是有符号也可以是无符号的。C标准并未明确指定char
类型默认为有符号还是无符号,这取决于具体的编译器和硬件平台。大多数情况下,char
被当作有符号类型处理,其取值范围为-128到127;而unsigned char
则是无符号的,取值范围为0到255。可以通过查看limits.h
头文件中的CHAR_MIN
值来确定char
类型是否被视为无符号整型。
整型在内存中存放的是补码。(原因是cpu只有加法器)
语句
空语句:
一个 ;
表达式语句:
在表达式后面加上;就是表达式语句
函数调用语句
复合语句
数组
一组相同类型元素的集合
数组的类型:例如创建一个数组 int ch[4]={1,2,3,4}; 这个数组的类型是 int[4],即去掉数组名就是数组的类型,其值是首元素在内存中的地址。
字符
一个字符占一个字节;
字符在电脑中以ascll码的形式存储
char用来接受字符,其实用int 也是完全可以的,char接受的其实就是字符的ascll码也就是一个无符号的整形。当我们想要找一个字符的后面几个字符时,可以直接 '字符'+n 这个'字符' 其实就是个数字,当你用%c输出时,输出的时这个整形所对应的·ascll码表中的字符。
字符串
char arr[10]={0};在C语言中,字符类型(char
)通常用ASCII码表示,而ASCII码中值为0的字符是空字符(null character)
字符串实质上是一个字符数组,他是一个数组。用 " " 双引号引起,所以我们在创建字符串时,要将他存到数组中,每一个字符都是当中的一个元素。当然也可应用char*创建一个指针变量来存储,此时存储的其实是首元素在内存中的地址,。如ch[5]="abcde";char* st="abcde"; ,事实上这个字符串的末尾是字符'\0', 编译器会根据首元素的地址找到后面的所有元素,直到找到'\0'为止。
值得注意的是字符串的末尾是默认有一个字符 '\0' ,这个字符用来表示字符串的结束。,这一点在有些时候会有妙用。当我们引入#include <string.h>后可以用strlen()直接来求字符串的大小。
1.strlen()函数:size_t strlen(const char* str);返回类型是无符号整型,形参类型是char*是一个地址。此函数的特点是可以从给定的第一个地址一直向后查找知道找到'\0'.
2.strcpy()函数:char*strcpy(char*destination,const char*source);就是将源头的字符串拷贝放到目的地空间中。拷贝的时候从source源头地址开始向后知道直到找到'\0','\0'也会被拷贝到detinatiion中;目标空间一定要能放的下;目标空间可修改。
注意当我们在使用函数传参时,我们传的是一个地址,所以依然可以用strlen()来数组的长度,不可以sizeof()来计算,因为此时sizeof()计算的是这个地址的大小,地址的大小跟编译器有关。
3.strcmp:int strcmp(const char* str1,const char*str2);如果第一个大于第二个返回正数,相等返回零,小于返回小于零的数。比较过程就是一对一对比较ASCII码值,注意不是按长度比较的。
4.strcat:char * strcat (char * des,const char * src)将一个字符串加到一个字符串的后面
以上四个函数是长度不受限的
strncpy;strncmp;strncat是有长度限制的字符串函数,其实就是函数传参的时候多传了字符串的长度的参数。
strstr:产找字串
来看有一个例子:char arr[]="abcdef";
char arr[]={'a','b','c','d','e','f'};
这两个是不一样的,第一个数组是有字符串创建的,其末尾是\0;但是第二个是由单个的字符放到数组中的,所以其大小就是字符的个数,没有\0。
逗号表达式
(....,...)逗号表达式会从左到右依次计算,取得最后一个表达式的值。
自定义函数
形参
形参是实参的一份临时拷贝。
实参和形参的名字可以相同也可以不同,因为都是局部变量,只是在他的哪一个范围内有效
在数组传参时,如果是一个一维的数组,形参可以不表明数组元素个数;如果是二维数组,那么行可以省略,列不能省略
函数的功能应该功能专一且分工明确
return语句:
return后面可以是一个数值也可以是一个表达式,如果是表达式,先执行表达式再返回表达式的值。
如果函数没有返回值但是序言提前结束,可以使用return;即可。
如果return返回的值跟函数的返回值不一样,系统会自动将返回值隐式转换成函数的返回值类型
return执行后,后面的代码将不会执行
在if分支语句中每一种情况都要有返回值。(会报警告:不是所有的控件路径都有返回值)
函数不写返回类型会默认返回整型
作用域和生命周期
作用域:1.局部变量的作用域只在局部可以使用
2.全局变量的作用域是整个工程
生命周期:从变量的创建到变量空间的回收是这个变量的生命周期
局部变量的生命周期:进入作用域变量创建生命周期开始,出作用域生命周期结束
全局变量的生命周期:是整个程序生命周期。
extern和static两个关键字
static:static修饰的变量变量叫做静态变量,其实编译器在编译代码的时候就已经为静态变量分配了地址,不是执行到创建语句才创建的
1.static修饰局部变量其实是改变了变量的生命周期生命周期改变的本质是改变了数据类型将,变量放到了静态区,在静态区的变量跟全局变量的生命周期是一样的,生命周期跟程序的生命周期是一样的,但是作用域还是没有变化,作用域还是该局部局部改全局全局。
2.当static修饰全局变量时,改变了全局变量的链接属性,使得外部链接属性变成了内部链接属性,这种变量只能在自己存在的.c文件中使用
3.static修饰函数:函数也是具有外部连接属性的,跟全局变量是类似的,用static修饰函数后,也是改变了连接属性,将外部连接属性变成了内部链接属性。
extern:是用来生命外部符号的;对于没有被static修饰的全局变量或者函数的声明可以使用extern,因为全局变量和函数默认是带有外部连接属性的,可以在其他的源文件使用,前提是用extern声明变量。
递归
如果递归无限的递归下去就会stack overflow 栈溢出;因为每一次函数调用都会为这次函数调用分配内存空间,是内存的战区上分配的,如果无限的递归调用函数就会栈溢出。
递归的思想:把一个大型复杂的问题层层转化成与文件相似的小规模问题;把大事化小;递归中的递是递推,归是回归。先递推出去再回归回来
注意:1.递归是存在限制条件的,当这个条件满足时,就不再递归。
2.每一次递归将越来越接近这个限制条件。
内存
内存分为栈区,堆区,静态区;
栈区主要存放函数参数,局部变量
堆区专门用来动态内存管理
静态区主要存放全局变量和静态变量
库函数
strlen()是库函数里的一个函数,是专门计算字符串长度的,只能针对字符串,从参数给定的地址,向后一直找'\0',统计'\0'之前出现字符的个数。注意是之前的所有元素,并不包括'\0'。
而sizeof()是操作符是用计算变量或者(类型所创建变量)所占空间的大小,不关心内存中存放的具体的内容。
操作符
当使用赋值运算符(=)时,表达式会将右侧的值赋给左侧的变量,并将该值作为整个表达式的结果返回。这意味着可以将赋值语句放在另一个表达式中,或者将其与其他表达式链接起来。
int x;
int y = (x = 5) + 1; // 将 5 赋给 x,然后将 5 作为表达式的值与 1 相加,结果赋给 y
这个表达式的返回值是6,y的值也是6
& 按位与(二进制)//意思是一个两个事件同时发生才是1,否则是0;也就是说两个二进制数对应着的每一位都是1结果才是1,否则是0;
| 按位或(二进制)//意思是只要有一个事件发生就是1,只有全是0的时候才是0。对应位存在1就是1
^ 按位异或(二进制)//对应的二进制位相同为0,相异为1;
例如:int a=3,b=-5; //那么我们在用与或异或的时候要先将数字转换成二进制(在计算机中存的是2进制补码)因为这里是int类型,4个字节,一个字节8bit,所以一共32bit;
也就是32位。
所以3的原码是00000000000000000000000000000011;因为3是正整数,所以原码跟补码相同00000000000000000000000000000011(3的补码)
-5的原码:10000000000000000000000000000101;
负数的补码是源码的符号位不变,其他位按位取反后+1;
-5的补码 11111111111111111111111111111011
3的补码 00000000000000000000000000000011
3 &(-5)= 00000000000000000000000000000011 (3)//注意这里是补码的形式,只不过正数的补码跟原码相同
3 | (-5) =11111111111111111111111111111011//这里是补码的形式
转换成原码,即-1后保持符号位不变其他位那位取反
即10000000000000000000000000000101(-5)
3 ^ (-5)=11111111111111111111111111111000//这里还是补码
源码为10000000000000000000000000001000
两个相同的数字异或是0;3^3=0
0跟任何数异或为任何数本身;0^a=a
3^3^5=5 3^5^3=5 得知异或支持交换律
请看如何交换两个数://不会导致溢出
int a=3,b=5;
a=3^5;
b=a^b;此时b是3;
a=a^b;此时a是5;
注意&|^只适用于整形。
//
<<
在C语言中,<<
表示左移运算符。
左移运算符<<
用于将一个数的二进制位向左移动指定的位数。这个操作对于每一个比特位来说,相当于乘以2的指定次幂。例如,若有变量x
值为3(二进制表示为0000 0011),则表达式x << 2
的结果是将x
的二进制位全部向左移动两位,移位后变为0000 1100,即十进制的12。
此外,左移运算通常用于快速对数字进行乘法运算,或者在处理位字段、制作掩码时调整位的位置。需要注意的是,与所有位运算一样,左移运算符只适用于整数类型的数据。
>>
在C语言中,>>
表示右移运算符。
右移运算符>>
用于将一个数的二进制位向右移动指定的位数。对于无符号数来说,这个操作相当于除以2的指定次幂。例如,若有无符号变量x
值为12(二进制表示为0000 1100),则表达式x >> 2
的结果是将x
的二进制位全部向右移动两位,移位后变为0000 0011,即十进制的3。
对于有符号整数,右移可以是逻辑移位或者是算术移位,这取决于具体的编译器实现:
- 逻辑右移:无论正负,高位都填充0。
- 算术右移:正数时高位填充0,负数时高位填充1,这样做可以保持负数的符号位不变,同时保持其绝对值的整除结果正确。
需要注意的是,与所有位运算一样,右移运算符只适用于整数类型的数据。
//
赋值操作符:=
//
复合操作符:
+= -= *= /= %= <<= >>= &= |= ^= ……
//
双目操作符(有两个操作数):+
单目操作符(有一个操作数):!(逻辑反操作,把真变成假,把假变成真 如:!1=0;)+ - &取地址操作符 sizeof() 计算的是类型或者变量所占内存的大小单位是字节 ,sizeof()返回值是无符号整形;
~对一个二进制数按位取反
--(前置和后置) ++(前置和后置)
*间接访问操作符(解引用操作符) (类型)强制类型转换
算数转换:
若算术运算符的操作数是整型,首先进行整型提升
如果其中一个操作数是long double类型,则另一个操作数也会被转换为long double类型
否则,如果其中一个操作数是double类型,则另一个操作数也会被转换为double类型
否则,如果其中一个操作数是float类型,则另一个操作数也会被转换为float类型
此时这两个数都是整型,再进行如下判断:
如果都是无符号类型或有符号类型,则会转换成优先级较大的那个类型
否则,如果无符号操作数的优先级>=有符号操作数的优先级,则转换为无符号操作数的类型
否则,如果有符号类型的值域能覆盖无符号类型,则转换为有符号操作数的类型
否则,都转换为无符号类型
指针
指针是内存中最小单元的编号,也就是地址。
口头语上的指针一般是指针变量,用来存放地址的。
内存再带你电脑中被切割成内存单元-------一个内存单元就是1byte字节,为了找到具体的某一个内存单元,每个内存单元都有一个编号;这个编号就是地址,也就是指针。地址也叫指针,指针就是地址,而地址是编号,所以指针就是编号。
指针变量里存放的是一个地址,而通过这个地址就可以找到对应的内存单元。
在32位机器下,一个地址是32为比特,也就是4个字节,所以指针的大小是4个字节。对所有的类型都是4个字节。在64位下则是8个字节。
那么指针类型的意义是什么呢?
指针类型并不能改变指针变量的大小,而是在*(解引用)的时候解出几个字节的空间,如果是char*类型的指针解出一个空间,如果是int*类型的空间则解出4给字节。所以指针类型决定了指针在被解引用的时候访问几个字节。可以理解成指针变量的类型其实是决定了指针最小单元,也就是,如果是int*其实是把内存看成了四个字节一份,每一份用首个字节的地址代替,它是每一次以四个字节位最小单位操作,每一个相邻的指针之间相差了四个字节。不同的指针类型决定了以类型相对应的字节大小为最小单位。
不同类型的指针变量对内存空间的解读是不同的,所以不能混用。
野指针:指针指向的位置是不可知的
1.指针未初始化
2.指针越界访问
3.指针指向的空间释放
NULL是0的意思,可以用来指针初始化;对于零地址是无法访问的
一个局部变量不初始化的额话放的是一个随机值。
指针加减整数就是访问这个指下几个或者上几个的地址
指针加键指针:指针减去一个指针得到的数值的绝对值是这两个指针之间元素的个数,注意不是字节数。并且,指向同一块空间的指针相减才有意义。
在C语言中,指针之间的比较可能体现在以下几个方面:
- 检查相等性:通过使用关系运算符
==
,可以判断两个指针是否指向内存中的同一个地址。这在某些情况下用于检查两个指针是否指向相同的变量或数组元素。 - 比较大小:如果两个指针指向同一个数组或者一段连续的内存区域,那么可以使用
<
、<=
、>
和>=
运算符来比较它们的地址值的大小。这样的比较通常用于遍历数组或者处理数组元素的排序等问题
指针和数组
对于一个数组arr;那么arr[i]与*(arr+i)完全等价;也就是说你可以随意对一个地址进行[i]
其实就是一个数组的访问。
数组是一组相同类型元素的集合
指针是一个变量存放的是地址。
数组可以通过指针来访问
二维数组名是第一行数组的地址,注意这是一个数组的地址,在使用函数传参时用 int(类型)
(*p)[一行的元素个数] 接收。其实就是数组地址传参。
对于一个存储数组地址的指针变量p,其类型为 数组类型(*)[数组元素个数] 。p+1实际上是跳过了一个数组的所有元素。例如:
int arr[5];
int (*p)[5]=&arr;
二级指针
就是指向指针的指针
二级指针变量存放的是一级指针变量的地址。例如 int** 后面一个*表明了这是在创建一个指针,而int*说明这个指针所指的元素的类型是int*
存放指针的数组就是指针数组,例如int* parr[10]={&a,&b,&c};这个数组存放的都是地址。
结构体
结构体是一些值的集合,这些值成为成员变量。结构体的每个成员可以是不同类型的变量。用于对复杂对性的描述。
任何一个变量/表达式都有两个属性:1.值属性 2.类型属性
数据存储
每个表达式都会有值属性和类型属性!!
对于一个字符串作为表达式的一部分,这个表达式的值是字符串首字符的地址。\0是字符串的结束标志
内存函数
1.memcmp()比较数据是否相等
int memcmp(const void*ptr1,const void*ptr2,size_t num);从开始的两个地址开始向后num个字节相互比较,返回类型是int;如果相等返回0;ptr1大于ptr2返回大于零的数;ptr1小于ptr2返回小于零的数;跟strcmp很类似,但是strcmp只能作用于字符串,而memcmp可以比较任意类型的数据。
2.memset()用于初始化。
void*memset(void*ptr,int value,size_t num);