一、操作符
1、<<、>>移位操作符
int a=2;
int b=a<<1; //移位操作符<<、>> ,转为二进制数然后移动
printf("%d\n",b);//b的值为4
2、 ! 逻辑反操作
//0表示假,非0表示真
int a=0;
printf("%d",!a);//本来a是0,为假,!a为真,值为1
//!把真变假,把假变真
such as
if(a){//如果a为真,进入if语句
......
}
if(!a){//如果a为假,进入if语句
......
}
3、sizeof
3.1 介绍
//计算类型或者变量的大小 ,是一个操作符,不是函数
//算出来的结果的类型是unsigned int
int i=-1;
i>sizeof(i)//比较大小,在计算的时候,先把i变成无符号整形,10000……0001,超级大,而sizeof(i)的结果仍然为4
int arr[10]={0};
printf("%d",sizeof(arr));//结果是40,计算的是数组的总大小,单位是字节
printf("%d",sizeof(arr[0]));//结果是4,表示数组第一个元素的大小
int sz=sizeof(arr)/sizeof(arr[0]);//结果是10,得到数组元素的个数
3.2 sizeof与strlen例题
sizeof(数组名),计算整个数组的大小
&数组名,数组名表示整个数组,取出的是整个数组的地址
除此之外,所有数组名都是数组首元素的地址
(1)整型数组
int a[]={1,2,3,4};
printf("%d\n",sizeof(a)); // 16
printf("%d\n",sizeof(a+0)); // 4 or 8 ,a+0是第一个元素的地址,计算的是地址的大小,在32位是4,64位是8
printf("%d\n",sizeof(*a)); // 4 , *a是数组的第一个元素,计算的是第一个元素的大小
printf("%d\n",sizeof(a+1)); // 4 or 8 , a+1是第二个元素的地址,计算第二个元素的地址的大小
printf("%d\n",sizeof(a[1])); // 4 ,计算的是第二个元素的大小
printf("%d\n",sizeof(&a)); // 4 or 8 ,&a是整个数组的地址,但是也是一个地址 ,计算的是一个地址的大小
printf("%d\n",sizeof(*&a)); // 16 , 取出整个数组的地址,再解引用,得到的是整个数组,计算的是整个数组的大小
printf("%d\n",sizeof(&a+1));// 4 or 8 ,取出数组的地址再+1,直接跳过该数组,直接到数组后的空间的地址,计算的是数组后的空间的地址的大小
printf("%d\n",sizeof(&a[0])); // 4 or 8
printf("%d\n",sizeof(&a[0]+1)); // 4 or 8 ,第一个元素的地址+1,计算的是第二个元素的地址
(2)字符数组
a、char arr[]={};
char arr[]={'a','b','c','d','e','f'};
printf("%d\n",sizeof(arr));// 6
printf("%d\n",sizeof(arr+0));// 4 or 8 ,表示第一个元素的地址,计算的是地址的大小
printf("%d\n",sizeof(*arr));// 1 ,计算的是第一个元素的大小
printf("%d\n",sizeof(arr[1]));// 1 ,计算的是第二个元素的大小
printf("%d\n",sizeof(&arr));// 4 or 8 ,取出的是整个数组的地址,计算的是地址的大小
printf("%d\n",sizeof(&arr+1));// 4 or 8 ,取出整个数组的地址再+1,直接跳过,计算的是数组后的空间的地址的大小
printf("%d\n",sizeof(&arr[0]+1));// 4 or 8 ,取出第一个元素的地址+1,计算的是第二个元素的地址的大小
printf("%d\n",strlen(arr));// 随机值 , strlen遇到\0才停下来,一直找不到,直到遇到\0,所以是随机值
printf("%d\n",strlen(arr+0));// 随机值,arr和arr+0都是首元素地址
printf("%d\n",strlen(*arr));// error ,传过去第一个元素是个char,而strlen的参数是char*,会报错
printf("%d\n",strlen(arr[1]));// error ,传的是第一个元素char类型,参数不匹配,报错
printf("%d\n",strlen(&arr)); // 随机值 ,传过来整个数组的地址 ,是一个char(*)[6],表示的是一个指针数组,数组里面放的都是地址,满足strlen 的参数要求,于是开始寻找\0,所以是随机值
printf("%d\n",strlen(&arr+1)); // 随机值-6,直接跳过本数组,和上一个类似
printf("%d\n",strlen(&arr[0]+1)); // 随机值-1
b、char arr[]="";
char arr[]="abcdef";//这种方式初始化,最后会有\0
printf("%d\n",sizeof(arr));//7
printf("%d\n",sizeof(arr+0));//4 or 8
printf("%d\n",sizeof(*arr));// 1
printf("%d\n",sizeof(arr[1]));// 1
printf("%d\n",sizeof(&arr));// 4 or 8
printf("%d\n",sizeof(&arr+1));// 4 or 8
printf("%d\n",sizeof(&arr[0]+1));// 4 or 8
printf("%d\n",strlen(arr));// 6
printf("%d\n",strlen(arr+0));// 6
printf("%d\n",strlen(*arr));// error
printf("%d\n",strlen(arr[1]));// error
printf("%d\n",strlen(&arr));// 6
printf("%d\n",strlen(&arr+1));//随机值
printf("%d\n",strlen(&arr[0]+1));// 5
c、char* p="";
char* p="abcdef";
printf("%d\n",sizeof(p));//4 or 8
printf("%d\n",sizeof(p+1));// 4 or 8
printf("%d\n",sizeof(*p));// 1
printf("%d\n",sizeof(p[0]));// 1
printf("%d\n",sizeof(&p));// 4 or 8
printf("%d\n",sizeof(&p+1));// 4 or 8
printf("%d\n",sizeof(&p[0]+1));//4 or 8
printf("%d\n",strlen(p));//6
printf("%d\n",strlen(p+1));//5
printf("%d\n",strlen(*p));// error
printf("%d\n",strlen(p[0]));// error
printf("%d\n",strlen(&p));// 随机值
printf("%d\n",strlen(&p+1));// 随机值
printf("%d\n",strlen(&p[0]+1));//5
4、++a与a++
++a //前置++,先++后使用
a++ //后置++,先使用后++
int a=10;
int b=++a;//结果为,a=11,b=11
int b=a++; //结果为,a=10,b=11;
5、exp1?exp2:exp3; 条件操作符
//条件操作符(三目操作符),若exp1成立,则结果为exp2,若不成立,则结果为exp3
6、逗号表达式
从左往右计算
7、~ 按位取反
//按位取反,将二进制数中,1变0,0变1
8、& 按位与
按(二进制)位与
例如:0000000000010000010001
0010000010001000010001
按位与: 0000000000000000010001,只有两个都为1,才变为1
^ //按位异或
相同为0,相异为1
9、注意事项
(1)关键字不能自己定义,也不能当成变量名使用
二、关键字
1、typedef 类型重定义
typedef unsigned int u_int;//意思是把unsigned int类型简写为u_int类型
unsigned int num=100;和u_int num=100;意思是一样的
2、static 静态
//静态,修饰局部变量、全局变量、函数
static int a;//保留上一个a的值
全局变量、函数,在其他源文件里也能被使用,具有外部链接属性,
但是被static修饰之后,就变为内部链接属性,只能在自己所在的源文件使用
3、define 定义常量和宏
#define a 100
#define add(x,y) ((x)+(y)) //定义宏
4、break
//在while语句中,用于永久的终止循环
5、continue
//while循环中跳过本次循环continue后面的代码,直接去判断部分,看是否继续进入循环
for循环中,直接跳过continue后面的代码,执行调整语句i++,再进入for循环
在do while语句中,两者结果都一样
6、EOF 文件结束标志
//end of file,其实就是-1,文件结束标志
Ctrl+Z //读取结束,相当于加了个EOF
7、goto
//三层if嵌套,从最内层直接跳到最外层,可以用goto
goto语句只能在一个函数范围内跳转 ,不能跨函数
8、const 常变量
//常变量,不能被修改,但本质上还是变量
修饰指针变量时,
1、如果放在*左边,修饰的是*p,表示指针指向的内容不能通过指针来改变 ,但是指针变量本身可以被修改
//const int* p=&a;
2、如果放在*右边,修饰的是指针变量p,表示指针变量不能被改变,但是指针指向的内容可以被改变
//int* const p=&a;
9、注意事项
三、函数
1、库函数
qsort
(1) 函数内容
void qsort(void* base, size_t num, size_t size, int (*compar)(const void*, const void*));
//库函数qsort有四个参数
//void* base, base中存放的是待排序数据中第一个对象的地址
//size_t num, 待排序数据中的元素个数
//size_t size, 待排序数据中,每个元素占的字节数
//int (*compar)(const void*, const void*) 函数指针compar,指向用来比较待排序数据中的两个元素的函数
//第一个const void*,存放第一个元素的地址
//第二个const void*,存放第二个元素的地址
qsort排序函数,需要引头文件#include <stdlib.h>,并且需要自定义比较函数
(2) 自定义比较函数
a、 整形比较函数
int compar_int(const void* e1, const void* e2)//整形数组排序
{
return *(int*)e1 - *(int*)e2;//强制类型转换成int
}
b、 字符比较函数
int cmp_char(const void*e1,const void*e2)
{
return strcmp((char*)e1,(char*)e2);
}
c、 结构体比较函数
int sort_by_age(const void* e1, const void* e2)//结构体数组排序,按年龄排序
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int sort_by_name(const void* e1, const void* e2)//结构体数组排序,按名字排序
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
perror
perror("自定义信息");
//直接把错误信息打印出来
void perror(const char* str);
isdigit
是数字则不返回0,不是数字则返回0
使用时需引头文件 #include <ctype.h>
isdigit(arr[i]);
int isdigit ( int c );
isalpha
是字母则不返回0,不是字母则返回0
使用时需要引头文件 #include <ctype.h>
int isalpha(str[i]);
int isalpha ( int c );
isupper
判断是否是大写字母
是大写字母则不返回0,不是大写字母则返回0
需引头文件#include <ctype.h>
int isupper ( int c );
isupper('X');
islower
判断是否是小写字母
是小写字母则不返回0,不是小写字母则返回0
需引头文件#include <ctype.h>
int islower ( int c );
isupper('x');
offsetof
//计算结构体各成员的偏移量
//offsetof函数,需引头文件#include <stddef.h>
#include <stdio.h>
#include <stddef.h>
struct s
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d\n",offsetof(struct s,c1));
printf("%d\n",offsetof(struct s,i));
printf("%d\n",offsetof(struct s,c2));
return 0;
}
默认对其数为8,结果为0,4,8
2、 字符串函数
gets
gets(str1);
strlen
strlen(str1);
遇到 \0 才停止 ,所以不能用来测char arr[] ={'a','b','c'};的长度
size_t strlen(const char* str);
函数返回值是unsigned int,是无符号数
such as:
strlen("abc")-strlen("abcdef")>0 ,is ture
strcpy
strcpy(str1,str2);
将str2的内容拷贝到str1中
char* strcpy(char* destination,const char* source);
字符串初始化时,str2需要有 \0
str1的空间需要比str2大
str1不能是常量字符串,必须可以被修改
such as: char* str1="xxxxxxxxx";这个str1是常量字符串,不能被修改
strcat
strcat(str1,"想加的字符串");//在str1后面追加字符串
strcat(str1,str2);
在str1后面追加字符串str2
char* strcat(char* destination, const char* source);
str1的空间要能放下追加的字符串
追加的字符串要有\0,而且\0最后会被加在str1上
不能自己追加自己
strcmp
strcmp(str1,str2);
比较str1,str2的大小
int strcmp(const char* str1,const char* str2);
若str1>str2,则返回 >0 的数
若str1==str2,则返回 0
若str<str2,则返回 <0 的数
strncpy
strncpy(str1,str2,len);
把str2的前len个字符,拷贝给str1
char* strncpy(char* destination, const char* source,size_t num);
strnact
strncat(str1,str2,len);//在str1后面追加len长度的str2
在str1后面追加str2的前len个字符
char* strncat(char* destination,const char* source,size_t num);
strncmp
strncmp(str1,str2,len);
比较str1和str2的前len个字符的大小
int strncmp(const char* str1,const char* str2,size_t num);
strstr
strstr(str1,str2);
//用来判断str2是否是str1的子串
//如果不是子串,返回空指针NULL
//如果是str1的子串,返回字串的地址(第一次出现的地址)
在使用时加上
char* ret=strstr(str1,str2);
//如果是,则ret==第一次出现的位置
//如果不是,则ret==0
char* strstr(char* str1,char* str2);
strtok
strtok(str1,str2);
分割字符串
str2放的是用作分隔符的字符集合
char* strtok(char* str,const char* sep);
sep是个字符串,定义了用作分隔符的字符集合
such as:
char arr[]="abc&rhg@ihj.ihe";
char* p="&@.";
strtok(arr,p);//将arr数组用 & @ . 三个字符隔开 ,将分割字符串直接变为 \0
如果strtok传入的第一个参数不是NULL,则从头开始分割,并且保存分割后更改的\0 的位置
如果此时strtok传入的第一个参数时NULL,则从刚刚保存的位置开始继续分割
strerror
strerror(errno);
在使用库函数时,如果使用错误,会返回错误码 ,strerror函数的作用是返回错误码所对应的错误信息
需要引用头文件 #include <errno.h>
char* strerror(int ennum);
3、 内存操作函数
memcpy
memcpy(arr1,arr2,num);
将arr2中前num个字节的数据拷贝到arr1中
什么类型都可以拷贝,整形,字符,结构体什么都行
void* memcpy(void* destination,const void* source,size_t num);
memmove
memmove(arr1,arr2,num);
处理内存重叠的情况
如果arr2的前num个字节的数据拷贝到arr1时,arr1不够大,则需要用memmove函数解决
void* memmove(void* destination,const void* source,size_t num);
memset
memset(arr,value,num);
把arr的前num个字节设置成value的值
void* memset(void* ptr,int value,size_t num);
memcmp
memcmp(arr1,arr2,num);
比较前num个字节的数据
int memcmp(const void* ptr1,const void* ptr2,size_t num);
4、动态内存函数
malloc
void* malloc(size_t size);
size指的是开辟的字节数
需引头文件 #include <stdlib.h>或者#include <malloc.h>
使用样例
#include <stdio.h>
#include <stdlib.h>
int main()
{
//申请一个包含10个整形的数组
int* p=(int *)malloc(10*sizeof(int));
if(p==NULL)
{
perror("main");
return 0;
}
//使用
int i=0;
for(i=0;i<10;i++)
{
scanf("%d",&p[i]);
}
for(i=0;i<10;i++)
{
printf("%d ",p[i]);
}
//回收空间
free(p);//free不会把p置为空指针,free只能释放动态内存申请的内存
//void free(void* ptr);
p=NULL; //手动置为空指针
return 0;
}
calloc
void* calloc(size_t num,size_t size);
需引头文件#include <stdlib.h>
//num是元素个数,size是一个元素所占的字节数
//每一个元素都被初始化为0
使用样例
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p=(int*)calloc(10,sizeof(int));
if(p==NULL)
{
perror("main");
return 1;
}
int i=0;
for(i=0;i<10;i++)
{
printf("%d\n",*(p+i));
}
free(p);
p=NULL;
return 0;
}
realloc
void* realloc(void* ptr,size_t size);
需引头文件#include <stdlib.h>
//ptr是要调整的内存地址
//size是调整之后新的大小
使用样例
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p=(int*)calloc(10,sizeof(int));
if(p==NULL)
{
perror("main");
return 1;
}
//使用
int i=0;
for(i=0;i<10;i++)
{
*(p+i)=5;//全部初始化为5
}
//发现申请的空间小了,需要再增加 10个int
//用realloc调整
int* ptr=(int*)realloc(p,20*sizeof(int));//新的大小包括之前申请的大小,即总大小
//新定义一个临时指针变量ptr,realloc函数在调整过程中,如果出现内存重叠的情况,则自动寻找一块新的空间,将原来的空间销毁。
//如果,调整的时候,堆区空间不够用,则返回空指针
if(ptr!=NULL)
{
p=ptr;//不是空指针时,进行赋值
}
//内存释放
free(p);
p=NULL;
return 0;
}
free
free(p);
void free(void* ptr);
free只能释放动态内存申请的内存
free不会把p置为空指针
p=NULL; //手动置为空指针
动态内存开辟创建常见的错误:
-
对NULL指针的解引用
-
对动态开辟的空间越界访问
-
使用free释放非动态开辟的空间
-
使用free释放动态内存中的一部分
-
对同一块动态内存多次释放
-
动态开辟的空间忘记释放
四、知识点
1、for循环详解
for(int i=1;i<=10;i++) //平时的循环
for(int i=0;i<10;i++) //数组的循环 ,左零右开
for(;;) //第一个部分省略,默认i=0,且i的值会保存上一次的,不会再初始为0
//判断部分省略,恒为真,陷入死循环
2、do while循环
do
{
循环语句
} while(表达式);
//do while语句至少执行一次,for语句可以一次都不执行
3、字符串数组初始化
char arr[]={'a','b','c'};//求字符串长度strlen时,返回值为随机值 ,这种不会有\0
char arr[]="abc"; //这种会有个\0
char* p="abcdefg";
4、二维数组的遍历
int arr[3][4] = { {1,2},{3,4},{4,5} };
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
#include <stdio.h>
int main()
{
char arr[3][6]={0};
int i=0;
for(i=0;i<3;i++)
{
scanf("%s",arr[i]);//多组字符串的输入
}
for(i=0;i<3;i++)
{
puts(arr[i]);//多组字符串的输出,puts函数自带\n
}
}
5、数组名详解
//数组名是数组首元素的地址
//但有两个例外
//1、求sizeof(数组名) ,此时数组名表示整个数组,计算的是整个数组的大小,单位是字节
//2、&数组名 ,此时数组名表示整个数组,取出的是整个数组的地址
printf("%p\n",&arr);//取出的是整个数组的地址
printf("%p\n",arr);//取出的是数组首元素的地址
printf("%p\n",arr[0]);//取出的是数组首元素的地址
6、数据存储
数据在内存中以二进制的形式储存
对于整数来说:
整数的二进制有3种存储方式:原码、反码、补码
正整数:原码、反码、补码三者相同
负整数:(1)先写原码,第一位是符号位,是负数则符号位是1
(2)反码:原码的符号位不变 ,其他位按位取反,1变0,0变1
(3)补码:反码+1
整数在内存中存储的是补码
对于浮点数来说:
根据IEEE 754标准
任意一个二进制浮点数V可以表示成下面形式 :
(-1)^s*M*2^E
(-1)^s表示符号位,当s=0时,V为正数;当s为1是,V为负数。
M表示有效数字,大于等于1,小于2
2^E表示指数位
7、大小端
大端字节序:
把数据的低位字节序的内容存放在高地址处,高位字节序的内容存放在低地址处
小端字节序:
把数据的低位字节序的内容存放在低地址处,高位字节序的内容存放在高地址处
such as:
int a=0x11223344;
//前面是高位字节序,越往后字节序越低
//地址的存放是由低到高,前面低,后面高
如果是11 22 33 44 这样存放,11是高字节序,存放在低地址处,即为大端字节序
如果是44 33 22 11 这样存放,44是低字节序,存放在低地址处,即为小端字节序
8、%u 整型提升
%u,打印无符号整形,进入的是补码,输出的是原码对应的数,需要整形提升,(char输出%u)
char类型,只有一个字节,只能存放8个比特位,即可存放-128~127,如果已经是127,再加1,会变成-128,一个圆圈,轮回
unsigned char的范围是0~255
%d,打印有符号整形
9、数组指针
9.1 一维数组
一般不用
int arr[10]={1,2,3,4,5};
int(*parr)[10]=&arr;//取出的是数组的地址
for(i=0;i<10;i++)
{
printf("%d ",*((*parr)+i));//指针数组的遍历 ,(*parr)是数组首元素的地址
}
9.2 二维数组
int arr[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
//二维数组的首元素是第一行,即{1,2,3,4,5}
//遍历的时候
void print(int (*p)[5],int r,int c)
//int (*p)[5],表示一个名为*p的指针,指向一个有5个元素的数组 ,数组中存放的是整形,即二维数组的首地址
{
int i=0;
int j=0;
for(i=0;i<r;i++)
{
for(j=0;j<c;j++)
{
printf("%d ",*(*(p+i)+j));
//p存放的是二维数组首元素的地址;即{1,2,3,4,5}的地址
//(p+i)指的是,二维数组中第一个元素,第二个,第三个……的地址;即{1,2,3,4,5}的地址,{2,3,4,5,6}的地址,{3,4,5,6,7}的地址
//*(p+i)解引用,指的是二维数组中第一个元素,第二个元素,第三个元素………;即{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}
//*(p+i)+j指的是二维数组中第i个元素中的第j个元素的地址;即 {1,2,3,4,5}中每个元素的地址
//*(*(p+i)+j)解引用,指的是二维数组中第i个元素中的第j个元素本身 ;即 {1,2,3,4,5}中的每个元素
}
}
}
int* arr[5];//表示一个整型数组 ,数组里面存放的是整形地址
int*(*parr)[5]=&arr;//取出数组的地址
int arr[5];//整形数组
int *parr[10];//整形指针数组,存放整形指针的数组
int (*parr)[10];//数组指针 ,表示一个指针指向一个数组 ,该数组有10个元素,每个元素的类型都是int
int (*parr[10])[5]; //数组指针数组,parr是一个存放数组指针的数组
//该数组能够存放10个数组指针
//每个数组指针能够指向一个数组,该数组有5个元素,每个元素都是int整形
10、函数指针
//&加函数名, 或者直接函数名
printf("%p\n",&function);
printf("%p\n",function);
int (*pf)(int,int)=&function;//定义了一个函数指针变量pf;类型,参数随时变化
printf("%d\n",(*pf)(3,5));//通过函数指针调用函数
printf("%d\n",function(3,5));
printf("%d\n",pf(3,5));//这种和(*pf)(3,5)等价,*可以省略
//pf和函数名是等价的
int (*pfarr[2](int,int))={function1,function2};//函数指针数组pfarr
int (*(*pfarr)[2])(int,int))=&pfarr;//pfarr是一个指向【函数指针数组】的指针,取出的是函数指针数组的地址
数组名!=&数组名
函数名== &函数名
11、数组传参
11.1 一维数组
void test(int arr[]);
void test(int arr[10]);
void test(int *arr);
void test(int *arr[10]);
void test(int **arr);
11.2 二维数组
void test(int arr[3][5]);
void test(int arr[][5]);
void test(int (*arr)[5]);
12、修改编译器的默认对齐数
#pragma pack(2)
修改默认对齐数,改为2或者其他值
VS编译器的默认对齐数为8
注意事项
五、说明
本博文每日更新,仅为博主学习记录所用,不包含全部操作符、关键字和知识点