C语言知识整理

作者整理不易,希望大家能给个关注和点赞,谢谢啦~~~~~~~

因为文章是作者自己整理的,参考了网上其他帖子和b站的视频教程,再结合书,可能会出现错误,欢迎大家指正~~~~~~~

数据类型
对于所用到的变量要求都是“先定义,后使用”。

整型

①十进制

②八进制 以0开头的数是八进制数字 0123代表八进制的123,即1*8**2+2*8**1+3*8**0

③十六进制 以0x开头的数是十六进制数 0x123代表十六进制的123,即1*16**2+2*16**1+3*16**0

④数值是以补码表示的。一个正数的补码与原码相同。负数的补码是反码+1,而反码是符号位不变其他位按位取反。1

⑤对于4个字节int类型,有符号位的整型取值范围:-2**31~2**31-1,无符号型:0~2**31-1

实型:小数、指数

注意指数中123e3或者123E3都代表123X10**3,但是e前面必须有数字,可以为整数,也可以为小数,小数点左边只能有一位非0的数字,后面必须为整数,如e3、2.1e3.5、.e3、e都是不合法的指数形式。12.908e10、0.123e10不属于规范化的指数形式。

符号常量

#define PRICE 30  //定义符号常量

①如果再用赋值语句给PRICE赋值是错误的,因为符号常量不占用内存,只是临时符号,预编译后这个符号就不存在,故不能对符号常量赋以新值

② 含义清楚

③ 需要改变一个常量时能做到“一改全改”。

操作符

算数操作符  “+ - * / %”

① + - *比较简单,不详细阐述

② /:除法 %:取模

#include <stdio.h>

int main(){
	int a = 5/2; //商2余1   a=2
	int b = 5%2; //商2余1   b=1
	printf("%f %d\n",a,b);
}

注意若想要将5/2返回的是2.5,那么5或者2变成浮点型,至少一个为浮点型。

#include <stdio.h>

int main(){
	float a = 5/2.0; //商2余1   a=2
	int b = 5%2; //商2余1   b=1
	printf("%f %d\n",a,b);
}

整数二进制表示形式

①原码、反码、 补码

② 正数的原码、反码、补码相同

③存储在内存的是补码,如下图,变量a = -1,在内存中存储为8个f,在16进制中一个f化成而进制则表示四个1,即ff ff ff ff(16进制) == 11111111111111111111111111111111

移位操作符

<<左移操作符

>>右移操作符

①算术右移:右边丢弃,左边补原符号位 (注意:移位具体是移动补码,再转化为原码则为打印的结果)

int main(){
	int a = -2;
	int b = a >> 1;
    //a的原码、反码、补码
	// 10000000000000000000000000000010--原码           打印结果为-2
	// 11111111111111111111111111111101--反码 符号位不变,其它位取反
	// 11111111111111111111111111111110--补码 = 反码 + 1
    
    //b的原码、反码、补码
	// 11111111111111111111111111111111--右移后的补码
	// 11111111111111111111111111111110--右移后的反码
	// 10000000000000000000000000000001--右移后的原码   打印的结果为-1
	printf("%d\n",b);
}
int main(){
	int a = -5;
	int b = a << 1;
	//10000000000000000000000000000101--原码           打印结果为-5
	//11111111111111111111111111111010--反码 符号位不变,其它位取反
	//11111111111111111111111111111011--补码 = 反码 + 1

	//11111111111111111111111111110110--右移后的补码
	//11111111111111111111111111110101--右移后的反码
	//10000000000000000000000000001010--右移后的原码   打印的结果为-10
	printf("%d\n",b);
}

②逻辑右移动:右边丢弃,左边补0

注意:对于移位置运算符,不要移动负数位,这个是标准未定义的,例如:

int num = 5;

num >> -1 //出错

位操作符

& 按位与      | 按位或      ^ 按位异或

int main(){
	int a = 3;
	int b = 5;
	//00000000000000000000000000000011   a  补码
	//00000000000000000000000000000101   b  补码
	
	//按位与 &
	//00000000000000000000000000000001   同时为1是才是1,其余为0
	//按位或 |
	//00000000000000000000000000000111   只要有有一个是1,就是1
	//按位异或
	//00000000000000000000000000000110   相同为0,异为1
	printf("按位与:%d\n",a&b);
	printf("按位或:%d\n",a|b);
	printf("按位异或:%d\n",a^b);
}

例子,不使用中间变量,交换a与b(经过三次异或即可)

a = 3   b = 5

a = a^b  -->  b = a^b -->a = a^b 第一次a^b产生的相当于密码,任意一个与密码异或就得到另外的数字。

a     011 001001101
b     101101011011
a^b  001011101

例子,计算整数在内存存储中(补码)1的数量

#include <stdio.h>
#include<math.h>
int main(){
	int a = 3;
	int i = 0;
	int count = 0;
	for (i=0;i<32;i++){
		if (1 == ((a>>i)&1) ){
			count++;
		}
	}
	printf("%d\n",count);
}

赋值操作符

① int a; = 10;int x = 0;int y = 20; 

a = x = y+1; 等价与 x=y+1;a=x;  支持,但不建议。

关系操作符

>、>=、<、<=、!=、==

复合赋值符

+=、-=、*=、/=、%=、>>=、&=、|=、^=

逻辑操作符

逻辑与:& 同时不为1 --->1

逻辑或:||  一个为1    --->1

单目操作符
①双目操作符:有两个操作数
②单目操作符:有一个操作数
!                    逻辑反操作
-                      负值
+                     正值
&                     取地址
sizeof              操作数的类型长度
~                     对一个数的二进制按位取反
--                    前置、后值-- 
++                  前置、后值++ 
*                     间接访问操作符(解引用操作符)

① 不同类型数据计算与sizeof表达式不参与计算

#include <stdio.h>

int main(){
	short s = 1;
	int a = 10;
	printf("%d\n",sizeof(s = a + 5)); //s为两个字节,a为4个字节,s的说的算  sizeof()为2
	printf("%d\n",s);    // s=1  sizeof内部表达式不参与计算!!!!!!!!!!!
}

② 按位取反~,需要把符号位也要取反,不同于取反码

int main(){
	int a = 0;
	int b = 3;
	///-------------------a-------------------//
	//00000000000000000000000000000000000 补码
	//~按位取反时需要把符号位也要转变,但是下面反码变为原码不用
	//11111111111111111111111111111111111 补码
	//11111111111111111111111111111111110 反码
	//10000000000000000000000000000000001 原码

	///------------------b-----------------//
	//00000000000000000000000000000000011 补码
	//~按位取反时需要把符号位也要转变,但是下面反码变为原码不用
	//11111111111111111111111111111111100 补码
	//11111111111111111111111111111111011 反码
	//10000000000000000000000000000000100 原码
	printf("%d\n",~a);  //~变量 符号位也要变
	printf("%d\n",~b);  //~变量 符号位也要变
}

③ 将某一位变成0/1

int main(){
	int a = 11;
	
	//00000000000000000000000000001011  //将倒数第三位变成1
	//00000000000000000000000000000100  //两个二进制取或即可 |
    //00000000000000000000000000001111  
	a = a | (1<<2);
	printf("%d\n",a);
	
	//00000000000000000000000000001111  //再将倒数第三位变成0
	//11111111111111111111111111111011  //两个二进制取与即可 &
	//00000000000000000000000000001011

	a = a&(~(1<<2));
	printf("%d\n",a);
}

运算符优先级

单目运算符>算数运算符>关系运算符>逻辑操作符>赋值操作符 

(a>b)&&(x>y) 等价与 a>b&&x>y
(a==b)||(x==y) 等价于 a==b||x==y
(!a)||(a>b) 等价与 !a||a>b
4&&0||2 值为1  (自左向右算)
5>3 && 8<4-!0 自左向右
①先算5>3 --->1
②再算!0 -->1
③再算4-!0--->3
④再算8<3---> 0
⑤最后算1&&0 --->0
注意:逻辑表达式的求解中,不是所有的逻辑操作符都被执行,只是在必须执行下一个逻辑运算符才能求出表达式的解时,才执行该运算符
① a&&b&&c 只有a为真时(非0)才判断b的值,只有a和b的值为真才判断c的值。只要a为假,后续都不用计算
② a||b||c 只有a,b都为假时,才判别c。若a为真,那么b,c就不判断。

③(m=a>b)&&(n=c>d)q

当a=1,b=2,c=3,d=4,m和n的初始值为1时,由于a>b的值为0,因此m=0,此时n=c>d不执行,因此n仍为1

struct stu{
	char name[10];
	int age;
	char id[20];
};

int main(){
	struct stu s1 = {"pd",18,"19225720338"};
	struct stu *ps = &s1;
	printf("%s\n",(*ps).name);  //".的优先级高于*",所以要加括号
	printf("%d\n",ps->age);     //   结构体指针->结构体成员名
	printf("%s\n",s1.id);
}

条件操作符(三目操作符)

exp1?exp2:exp3

表达式exp1是否成立,若成立,exp2的返回值就是整个表达式的返回值,否则exp3。

int main(){
	int a = 3;
	int b = 4;
	b = a>b ? 10:15;
	printf("%d\n",b);   15
}

 比较两个数字的大小

int main(){
	int a = 3;
	int b = 4;
	int max;
	max = a>b ? a:b;
	printf("%d\n",max);
}

逗号运算符

一般形式: 表达式1,表达式2,.....表达式n

求解过程:先求解表达式1,一直求解到表达式n,最终整个表达式的值就是n的值

注意:逗号运算符是的优先级是所有运算符中最低的

如:

① a = 2,5,9*9     /*a的值为2*/  先赋值

② b = (2,5,9*9)  /*a的值为81*/

int main(){
    int a = 1;
    int b = 2;
    int c = (a>b,a=b+10,a,b=a+1); 
    printf("%d\n",c);   //返回13
}

错误的表达式

c +--c

操作符的优先级决定了--在+的前面,但我们并不知道+左边的操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,有歧义

a*b + b*c + c*d

执行顺序有歧义,表达式有问题,可能先执行a*b、b*c、c*d,在执行两个加号,也可能先执行a*b,b*c,在执行第一个加号,在执行c*d,最后执行第二个加号,有歧义。

int i = 1;

int ret = (++i) + (++i) + (++i)

因此,我们写出来的表达式如果不能通过操作符的属性确定唯一的计算路径,那么这个表达式是存在问题的。

整型提升

① 表达式中的字符和短整型操作数在使用之前被转换为普通整形,这种转换称为整型提升

② 意义:整型提升的意义在于:表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。

③ 整型提升类型
有符号的:整型提升时是按照变量的补码被截断时的最高位是什么进行补位的,如果截断后最高位(即最左面)的一位数为 1 则在最高位前补 1 ,如果最高位是 0 则在前面补 0 ,补够32位即int类型即可。

无符号的: 直接在被截断的前面补 0 即可。

④ 例子:

int main(){
	char a = 3;
	//00000000000000000000000000000011
	//00000011  -截断后存放到a中
	char b = 127;
	//00000000000000000000000001111111 2**6
	//01111111 -截断后存放到b中

	char c = a+b;
	//00000011 
	//00000000000000000000000000000011 整型提升

	//01111111 
	//00000000000000000000000001111111 整型提升

	//将两个二进制提升后相+
	//00000000000000000000000010000010 
	//10000010 相加后截断存储到c中

	printf("%d\n",c);
	//11111111111111111111111110000010 补码
	//11111111111111111111111110000001 反码
	//10000000000000000000000001111110 原码  -(2**7-2)=-126
}

⑤ 例子

int main(){
	char a = 1;
	printf("%u\n",sizeof(a));   //没有计算,仍为1个字节
	printf("%u\n",sizeof(+a));  //+a运算后 整型提升为4个字节
	printf("%u\n",sizeof(~a));  //~a运算后,整型提升为4个字节
}

算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非某一个操作数的转换为另外一个操作数的类型,否则操作就无法进行,下面的层次体系为寻常算术转换。

long double

double

float

unsigned long int

long int

unsigned int

int

两种定义字符串数组方法导致大小与字符串长度不一

 第二种比第一种少一个'\0'

#include <Stdio.h>
#include <string.h>
int main(){
	char arr1[] = "cyw";
	char arr2[] = {'c','y','w'};
	printf("%d %d\n",sizeof(arr1),sizeof(arr2));
	printf("%d %d\n",strlen(arr1),strlen(arr2));
}

二维数组定义时不能省略列

如int a[2][] = {{1,2,3},{1,2,3}}

函数设计:“高内聚、低耦合”原则。

函数的形参与局部变量是存放在栈中保存

全局变量与局部变量(变量的作用域角度分类)

(1)局部变量

① 局部变量只在所在定义含函数内有效,函数外部无法调用。

② 不同函数中可以使用相同的名字的变量,但它们在内存占用不同的空间,代表不同对象,互不干扰。

③ 形式参数也是局部变量

 (2)全局变量

① 函数之外定义的变量称为外部变量,即全局变量。

② C程序设计人员有一个不成文的约定,将全局变量名的第一个字母用大写表示。

③ 全局变量可以被其他文件引用(静态全局变量不可

(3)建议少用全局变量

① 如果在同一源文件中,全局变量与局部变量同名,则全局变量被“屏蔽”。

② 全局变量在程序执行全程都要占用存储单元。

③ 函数移植性低,因为函数执行时可能依赖其所在的外部变量,若要移植,那么就要联通函数所需的全局变量也要同一移植,因此划分模块时要求模块的”内聚性“强,与其他模块”偶合性低“。

④ 全局变量过多可能会降低程序的清晰性。

变量的存储类别(从变量值的存在的时间分类)

(1)存储空间

① 程序区 ② 静态存储区 ③ 动态存储区

(2)C语言当中每一个变量都有两个属性

① 存储的类型 ② 数据类型

(3)存储类型种类

① auto 自动的 ② static 静态的 ③ register 寄存器的 ④ extern 外部的

(4)auto变量 

① C语言中的变量默认都是auto类别,并存储到动态存储区中。

② auto可以省略。

(5)static 声明局部变量

① 静态局部变量的值所在的函数结束后保留原值,即占用的存储单元不施放,再下一次该函数调用时,该变量的值是上一次变量结束时的值。

(6)register变量

① C语言语序局部变量的值放在CPU中的寄存器中,需要时直接从寄存器中取出来参加运算,不必从内存中取。

(7)extern声明外部变量

① 编译时遇到extern时,先在本文件中找外部变量的定义,若找到,就在本文件中扩展作用域;若找不到,就从链接时从其他文件中找到外部变量的定义。

(8)static声明外部变量

① static声明的全局变量仅限于被本文件引用,不能在其他用extern引用该变量。

(9)静态局部变量与动态局部变量两种声明

① 静态局部变量存储在静态存储区中,程序整个运行期间都不施放,只赋值一次。

② 动态局部变量存储在动态存储区中,函数调用后即施放。

(10)寄存器 

①只有动态局部变量和形参才能作为寄存器变量,其他都不行,如全局变量。

(11)变量声明与定义

① 举例说明,“int a”即是声明,又是定义,“extern a”仅声明,不是定义。

② 创建存储空间的肯定是定义,不创建存储空间的肯定不是定义,为声明。

(11)存储类别注意事项

① 只有局部自动变量和形式参数才能作为寄存器,其他不行。

② 全局变量与静态局部变量不能定义为寄存器变量。

③  定义局部变量不赋值的话,静态局部变量自动赋值为0(数值类型)或空字符(字符串变量)。对于自动变量不赋值则它的值为一个不确定的值。

④ 按变量值存放的位置分类

    动态存储区:自动变量(动态局部变量)

    静态存储区:静态局部变量,全局变量(静态外部变量、外部变量:动态外部变量)

    CPU:寄存器

⑤  按作用域分类

    局部变量:静态局部变量、动态局部变量、寄存器、函数形参。

    全局变量:外部变量、静态全局变量、

⑥ 按照生存期区分

    动态存储 (调用函数时候存在)

    静态存储(整个程序运行时都存在)

内部函数与外部函数

① 内部函数只能被本文件中的其他函数调用,如 static int fun(int a)

②外部函数能够被其他文件函数调用,如 extern int fun(int a) extern可省略

指针

指针是一个地址,指针变量是存放地址的变量。

指针的访问方式

直接访问:直接通过变量名访问变量值,如下图(a)

间接访问:已经知道变量i的地址,根据此地址直接对变量i的存储单元进行访问,如下图(b)

指针运算符
① &:取地址运算符
② *:指针运算符("间接访问"运算符),取其访问的内容
③ &与*两个运算符的优先级别相同,但按自由向左的方向结合
     如:int arr[5] = {};
            int *point = a
            &*point 表示a的地址
            *&a 表示变量a 即 *&a与a等价
          (* point++)等价于 a++ 括号是必要的,若没有括号。即* point++  ++与*同一个优先级,没有括号则将指针向右移动,在取内容,此时point不再指向a                                                           

④*(point++) 先*point,再point++   

⑤*p(++point) 先point++ 再*p

⑥++(*p) 表示p所指向的元素值加1

⑦*(point--)相当于a[i--],先对point进行“*”运算,再使自减

⑧ *(++point)相当于a[++i],先对point进行自加,再作“*”运算
注意指针变量只能存放地址,不能将一个整数或者其他非地址类型的数据赋值给一个变量

指针类型

指针类型决定了指针进行解引用操作时,能够访问空间大小、指针步长不一,具体如下:

指针类型        访问字节权限指针走一步步长
int*p *p能够访问4个字节 4
char*p *p能够访问1个字节1
double*p *p能够访问8个字节8

指针大小

int main(){

	int *a;
	double *b;
	float *c;
	char *d;
	printf("%d\n",sizeof(a)); 
	printf("%d\n",sizeof(b));
	printf("%d\n",sizeof(c));
	printf("%d\n",sizeof(d));  //打印都为4

}

野指针

概念:野指针指的是指针指向的位置是不可知的、随机的、不确定的。

原因:① 指针未初始化

#include <stdio.h>
int main()
{
	int *p; //野指针 因为局部动态变量未初始化,默认为随机值
	*p = 20;
	return 0;
}

         ② 指针越界访问

int main(){
	int arr[10] =  {0};
	int *p = arr;
	for (int i = 0;i<12;i++){
		*(p++) = 1; //当指针指向的范围超出arr范围时,指针为野指针
	}
}

        ③ 指针指向的内存空间被施放

#include <stdio.h>
int* test()
{
	int a = 0;
	int *p = &a;
	return p;
}
int main(){
	int* p = test();   //调用test函数结束后,变量a在内存块中已经被施放,现在已经被系统使用
	printf("%d\n",p); 
}

指针运算小结

① 指针变量加减一个整数(p++、p--、p+i、p-i、p+=i、p-=i)

② 指针地址变量可以相减(在同一个数组中,表示两个指针间元素的个数)

#include <stdio.h>
int main(){
	int arr[10];
	printf("输出指针之间元素个数:%d\n",&arr[9]-&arr[0]);  // 为9个
	return 0;
}

③ 两个指针变量比较(在同一个数组中,靠前的元素地址小于后面元素的地址)

注意:之间运算中允许指向指针向数组最后一个元素后面那个内存位置的指针比较,但是不允许与指针指向第一个元素之间进行比较

下面的代码错误,因为a是第一个元素的地址,是指针型常量,所以既然是一个常量,不可以子增减。

//不可运行
int main(){
    int a[5] = {1,2,3,4,5};
    for (a;a<(a+10);a++)
        printf("%d",a);

}

//可运行
int main(){
	int a[5] = {1,2,3,4,5};
	int *p = a;
	for (a;p<(a+5);p++)
		printf("%d",*p);
}

数组名

数组名除了以下两种情况,其他都表示首个元素地址:

① &arr &数组名不是首元素的地址,而是表示整个数组的地址

② sizeof(arr) -sizeof(数组名) -数组名表示整个数组 -size(数组名字)计算整个数组的大小

对①进行解释:

#include <stdio.h>
int main(){
	int arr[5] = {0};
	printf("%p\n",arr);    // 取出数组首个元素地址
	printf("%p\n",arr+1);

	printf("%p\n",&arr[0]); // 取出数组首个元素地址
	printf("%p\n",&arr[0]+1);

	printf("%p\n",&arr);      //取出整个数组地址
	printf("%p\n",&arr+1);      
}

arr+1 ---> 位移4

&arr[0]  + 1  --->位移4

&arr +1 --->位移=14(16进制) = 20

一个数组+1 跳跃整个数组,因此&arr取出来的是整个数组的地址

利用指针求数组长度

#include <stdio.h>
int main()
{
	extern int my_strlen(char* arr);
	char arr[] = "bit";
	int len = my_strlen(arr);
	printf("字符串长度为:%d\n",len);
	return 0;
}
int my_strlen(char* arr){
	char* start = &arr[0];
	char* end = &arr[0]; 
	while (*end != '\0'){
		end++;           //最后end指向'\0'
	}
	return end-start;    //计算两个指针之间的数量 即长度   
}

 多级指针

多级指针指的是指向指针的指针,下面代码为二级指针的创建与引用。

int main(){
	int a = 0;
	int * pa = &a;         //指向变量a 
	int* * ppa = &pa;      //指向指针变量pa
	printf("%d\n",**ppa);  //对ppa两次解引用操作获取到变量a的值
	return 0;
}

 指针数组

指针数组指的是存放指针的数组,就类似与存放整形的数组叫整形数组,下面代码为指针数组的应用。

int main(){
	int i=0;
	int a=0,b=1,c=2;
	int *arr[3] = {&a,&b,&c};
	for (i=0;i<3;i++){
		printf("%d ",*arr[i]);
	}
}

 函数实现多个返回值

c语言中实参向形参得数据传递是“值传递”,单向传递,只能由实参传给形参,形参不能传给实参。

① 在主函数中定义n个变量,用n个指针指向该n个变量

② 将指针变量作为实参,将这n个变量地址传给所调用的函数的参数

③ 通过形参指针变量,改变该n个变量值

④ 主调函数中就可以使用这些改变了值得变量

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值