****************************************************************************************************************************************************************************************************************
操作符解释:
1,算术操作符:
‘+’、‘-’、‘*’、‘/’、‘%’
%取模操作符只能用于整数,
/除法操作符,两个操作数均是整数时为整数除法,有一个是浮点数则执行浮点数除法。
如:int a = 6/5;//->a==1
double b = 6/5;//->b==1.000000
double c = 6.0/5//->c==1.200000,即实际运算与定义的储存类型无关
2,移位操作符
左移‘<<’、右移‘>>’
左移操作符:
如: int a=2;
int b = a << 1;//a向左移动一位,结果存入变量b中,结果b为4
正整数2在内存中存放的补码为: 00000000000000000000000000000010
向左移动一位,左边的首位0去掉,右边的缺一位补0:00000000000000000000000000000100 ->4(十进制)
右移操作符:
右移时分为算数右移和逻辑右移。
算数右移:右边丢弃,左边补符号位。
逻辑右移:右边丢弃,左边补0。
如:int a = -10;
int b = a >> 1;//a向右移动一位,结果存入变量b中
正整数10在内存中的补码为11111111111111111111111111110110//若为负数则原码与补码不同。
算术右移: 11111111111111111111111111111011 ->-5
逻辑右移: 01111111111111111111111111111011 ->2,147,483,643
对移位操作符移动负数位,这时C语言未定义的,不要使用。
3,位操作符
按位与‘&’、按位或‘|’、按位异或‘^’
位解释为二进制的一位。
&按位与,全为1时结果才为1,其他情况结果均为0
如:int a = 3;// 00000000000000000000000000000011
int b = 5;// 00000000000000000000000000000101
int c = a & b;// 00000000000000000000000000000001->1(十进制)
eg:找出一个整数的二进制的1的个数。
#include <stdio.h>
int main(){
int a=1;
int cnt=0;
for(int i=0; i<31; i++){
if((a&1) == 1){//整数的二进制数的1的个数
cnt++;
}
a=a >> 1;
}
printf("%d\n",cnt);
return 0;
}
|按位或,全为0时结果才为0,其他情况结果均为1
如:int a = 3;// 00000000000000000000000000000011
int b = 5;// 00000000000000000000000000000101
int c = a | b;// 00000000000000000000000000000111->7(十进制)
^按位异或,不同为1,相同为0。一个数a与另一个数b异或两次结果仍是a本身,数a与0异或结果仍为数a。
如:int a = 3;// 00000000000000000000000000000011
int b = 5;// 00000000000000000000000000000101
int c = a ^ b// 00000000000000000000000000000110->6(十进制)
eg:交换两个数a=3,b=5,要求不使用第三个变量。
#include <stdio.h>//此法适用于a,b相加的结果不超过类型范围的情况,有缺点
int main(){
int a=3;
int b=5;
a = a + b;//变量a储存a与b的和
b = a -b;//变量b储存原来a的值
a = a - b;//变量a储存原来b的值
return 0;
}
#include <stdio.h>//此法适用于a,b相加的结果不超过类型范围的情况,有缺点
int main(){
int a=3;
int b=5;
a = a ^ b;
b = a ^ b;//3 ^ 5 ^ 5的结果3存入到b中
a = a ^ b;//3 ^ 5 ^ 3的结果5存入到a中
return 0;
};
4,赋值操作符
= += -= *= /= %= <<= >>=
‘=’是赋值,‘==’是等号。
int a = a + 4;//等价于a+=4;
连续赋值:
如:int b = a = a+1;//等价于a = a + 1; b = a;
连续赋值不好进行调试,阅读不便。
5,单目操作符
!(逻辑反) -(负值) +(正值) ~(按位取反) sizeof(操作数的类型长度,按字节计算) -- ++ &(取地址) *(间接访问操作符或解引用操作符) (类型名)强制类型转化
‘i’一般用于条件判断,0的反是1,非0的反是0;
如:
#include <stdio.h>
int main(){
int flag =0;
if(flag){
printf("Yes\n");//条件为假,语句不执行
}
if(!flag){
printf("YesYes\n");//条件为真,语句执行
}
return 0;
};
‘~’对二进制位的操作,类比按位与、按位或、按位异或。最高位也参与取反。
如:int a = 13;//00000000000000000000000000001101
int b = ~a; //11111111111111111111111111110010 -> b==-14;
‘sizeof’是单目操作符,可求内存大小。
如: int a =13;
int b = sizeof(a);//结果为4,是一个int类型变量所占字节个数,也可写为int b = sizeof a; 不需要加括号
int arr[10]={0};
int c = sizeof(arr);//结果为40,是十个int类型变量所占字节个数
int g = sizeof(int [10]);//结果为40,数组的类型实际上是去掉数组名(arr)之后留下的(int [10])。
如:
#include <stdio.h>
int main(){
short s = 5;
int a = 10;
printf("%d\n",sizeof(s = a + 2));//输出为2,short占2字节,等价于printf("%d\n", sizeof(s));。
printf("%d\n",s);//输出为5,上面表达式没有计算
return 0;
};
‘++’
int a = 13;
++a;//前置++,先++在使用
a++;//后置++,先使用,在++
‘&’取地址操作符,单目操作符,与按位与操作符区分
‘*’解引用操作符或间接引用操作符
如:#include <stdio.h>
int main(){
int a =13;
int *p = NULL;//定义int类型的指针变量,此时 * 是定义
p = &a;//整型指针变量p中存放整型变量a的地址
printf("%d\n",*p);//此句输出为13,*p与a等价。此时 * 是解引用
int a =1;
char b=1;
double c=1;
float d=1;
int *pa =&a;
char *pb =&b;
double *pc=&c;
float *pd=&d;
printf("%d\n",sizeof(pa));
printf("%d\n",sizeof(pb));
printf("%d\n",sizeof(pc));
printf("%d\n",sizeof(pd));//输出相同,为4或8(与具体系统有关),求的是指针变量的大小,不论是那种类型的指针变量,储存的地址形式都相同,内存的地址(指针常量)形式是确定的
}
‘(类型名)’,
如:#include <stdio.h>
int main(){
int a = 3.14;//浮点型转换为int型会损失精度,产生警告。
int b =(int)3.14;//强制类型转换,没有警告。
return 0;
}
6,关系操作符
=(赋值) ==(相等) <、>、<=、>=、!=(不等于)
字符串比较不能用==,要用字符串函数
7,逻辑操作符
&&(逻辑与) ||(逻辑或)
首先和按位与(&)、按位或(|)区分开。
逻辑与:全真为1,其它为0。
逻辑或:全0为0,其它为1。
短路现象:对于逻辑与:
如: #include <stdio.h>
int main(){
int a=0,b=3,c=2,d=4;
if(a && b && c && d)//先看a && b,a==0,则最终表达式结果一定为0,后面表达式不再计算
printf("111\n");
else{
printf("000\n");//最后输出为000
}
}
对于逻辑或:
如: #include <stdio.h>
int main(){
int a=5,b=3,c=2,d=4;
if(a || b || c || d)//先看a || b,a == 5,则最终表达式结果一定为1,即为真,后面表达式不再计算,输出为111。
printf("111\n");
else{
printf("000\n");
}
}
8,条件操作符
? : (三目操作符)
如:#include <stdio.h>
int main(){
int a =2;
int b = 4;
printf("%d\n", a>b?a:b);//如果a>b表达式成立,则表达式结果为a,否则为b,输出为4。
return 0;
}
9,逗号表达式
逗号表达式,从左向右依次计算,整个表达式的值是最后一个子表达式的值。
#include <stdio.h>
int main(){
int a =2;
int b = 4;
printf("%d\n", (a=a+3,b=b-2));//表达式a = a + 3计算,但整个表达式(a=a+3,b=b-2)的值是表达式(b=b-2)的值,结果为2
b = 4;
printf("%d\n", b=b-2);//结果为2
}
10,下标引用、函数调用和结构成员访问
[ ]下标引用操作符(运算符)
操作数:一个数组名+索引值,类比二元操作符,1 + 2。
如: #include <stdio.h>
int main(){
int a[10];//创建数组
a[0] = 10;//使用下标引用操作符为数组第一个单元赋值
}
( )函数调用操作符,接受一个或多个操作数,第一个操作数是函数名,剩余的操作数是函数的参数。
如: #include <stdio.h>
int max(int a, int b){//函数定义
return a > b ? a : b;
}
int main(){
int a = 3;
int b = 4;
max(a, b);//函数调用,输出二者中较大的那个数。
printf("%d\n",t);
return 0;
}
结构成员访问操作符:对于非指针变量使用圆点操作符,指针变量还可以使用箭头操作符
如:
#include <stdio.h>
struct student{
char name[20];
int age;
double mathscore;
};
int main(){
struct student a = { "weihe", 20, 90};
printf("%s\n",a.name);//使用圆点操作符访问结构体成员name数组
struct student *p = NULL;
p = &a;
printf("%d\n", (*p).age);//使用指针解引用访问结构体成员age
printf("%d\n", p->age);//使用箭头操作符访问结构体成员age
return 0;
}
11,表达式求值
求值顺序由操作符的优先级和结合性决定。有些表达式的操作数再求值的时候可能要转换为其他类型。
12,隐式类型转换
C的整型算术运算是以缺省(sheng)整形类型的精度来进行的。
为了达到这个缺省(sheng)整形类型的精度,表达式中的字符和短整型操作数(2字节)在使用之前被转换为普通整型(int或unsigned int),这种转换称为整形提升。
整形提升的意义:
表达式的整型运算在CPU相应的运算器件内执行,CPU内整型运算器的操作数的字节长度一般是int的字节长度,同时也是CPU的通用寄存器的长度。
即使是两个字符型变量相加,CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU难以直接实现两个8bit字节的直接相加运算(虽然机器指令中可能有这种字节相加指令)。
整形提升过程:
按照变量数据类型的符号位提升。
正数: char a = 1;//00000001(补码) ->(整形提升) 00000000000000000000000000000001
负数: char b = -1//11111111 (补码)-> (整形提升)11111111111111111111111111111111
如: #include <stdio.h>
int main(){
char a = 3;
char b = 127;
char c = a + b;// a: 00000011 -> 00000000000000000000000000000011
// b: 01111111 -> 00000000000000000000000001111111
c: 00000000000000000000000010000010 ->(截断) 10000010
printf("%d\n", c);/ c: 10000010 -> 11111111111111111111111110000010(反码)->(求原码)10000000000000000000000001111110 即为-126
return 0;//以int型输出是也会发生整型提升,对于unsigned char,整型提升时高位均补0
}
#include <stdio.h>
int main(){
char c =0;//sizeof返回类型为size _t,是unsigned int,打印时要用格式符%u
printf("%u\n",sizeof(c));结果为1
printf("%u\n",sizeof(-c));结果为4,sizeof运算符括号里的表达式(-c)虽然不参与运算,但有类型属性,为整形提升后的int
printf("%u\n",sizeof(+c));结果为4
printf("%u\n",sizeof(!c));结果为4
return 0;
}
****************************************************************************************************************************************************************************************************************
字符串
相关函数
头文件#include <string.h>
求长度
char s[10] = "hello world";
strlen(s),返回字符的个数
sizeof(s),返回字符串大小,包括'\0'
复制函数
char s1[10];//字符数组
char s2[10] = "hello";
strcpy(s1,s2),把字符串s2整体复制到s1中,但s1中要有足够的内存
strncpy(s1,s2,n),对s2最多n个字符成立
比较函数
strcmp(s1,s2),相等返回0,s1>s2返回正整数,s1<s2返回负整数
strncmp(s1,s2,n),字符串s1中最多n个字符与字符串s2比较,同上
连接函数
char s1[10];//字符数组
char s2[10] = "hello";
strcat(s1,s2),把s2中字符添加到s1中有效字符之后,s1要有足够的内存
strncat(s1,s2,n),对s2最多n个字符成立
逆置函数(倒序)
char s[10] = "hello";
strrev(s),
输出函数
sprintf(s,格式字符串,输出项列表),输出到字符串s中,不在屏幕上显示
输入函数
sscanf(s,格式字符串,输出项列表),从字符数组s中读取,而不是从键盘读取,
字符串数组与函数
****************************************************************************************************************************************************************************************************************
数组
&arr//指数组的地址,(&arr+1)加了整个数组的长度
arr//指数组首元素的地址,(arr+1)加了一个数组元素的长度
arr[0]//指数组首元素的地址
二维数组的函数调用
void pow(int a[][10])//一个调用二维数组的函数
int arr[10][10];
pow(arr);//只写数组名
****************************************************************************************************************************************************************************************************************
刷新函数
memset(数组名,0,sieof(数组名));
****************************************************************************************************************************************************************************************************************
指针(间接引用)(地址)
指针常量:计算机内存中的事先实现好的编号即地址,不可改变。
指针变量:指针类型,储存地址的变量,可以改变,指针变量自身也占有一定内存。不与指针常量混淆时简称为指针。
指针的大小:
指针在32位系统中占4字节,在64位系统中占8字节,与地址占内存的大小有关。
而在同一系统中地址由多个相同的bit构成
变量(int,float,double,char等)占用内存中的字节,首个字节的地址是该变量的地址
初始化
声明指针时必须对其进行初始化(为0,NULL,已定义变量地址)。
如:
void *p = NULL;//可以指向任何类型的指针,未知指向变量的数据类型,不能进行间接寻址运算
int *p = NULL;//不指向任何对象
int *p = 0;
int a = 1;
char *p = &a;//指向变量a的地址
间接寻址运算(运算符*,&)
*:解引用操作符,访问指针p所指的变量a。
&:取(返回)变量a的地址。
int a = 1;
int *p = &a;//此时*为指针声明符
有*p与a等价,p与&a等价
指针的算术运算
加法运算:(得到另一个地址)
int a[4]={4,5,3,1};
int *p = a;//或p = &a[0];
有(p + 1) == &a[1];.......(p + i) == &a[i];
减法运算: (得到地址之间的距离,用元素的个数表示)
int a[4]={4,5,3,1};
int *p = &a[0];
int *q = &a[3];
则p - q == 3.//结果可以为负整数,即地址相减后除以类型所占的字节
地址之间的距离==sizeof(int)*3.
指针比较:(指向相同数组时才有意义)
int a[4] = {5, 4, 3, 2, 1};
int *p = &a[0];
int *q = &a[4];
for(; p<=q; p++){
printf("%d\n", *p);//循环输出数组各元素
}
指针与数组:
int a[4];
由数组名a可知数组a的内存大小并得到数组a的首元素的第一个字节的地址(即数组名a是指向a[0]的一个指针常量,,指针常量,不可更改)。
int *p = a;
int i;
一个指针p与数组a关联之后数组a中的元素表示方法可以为:(以指针p指向数组首元素为例)
数组下标表示: a[i];
数组偏移量表示: *(a + i);//偏移量,即i
指针下标表示: p[i];
指针偏移量表示: *(p+i);
数组作为函数的参数:
形参数组(定义的函数中,非main函数)本质上是一个相应类型的指针变量,不是指针常量.
例如
int a[4];//实参
函数头void sum(int a[], int n)//形参a[]获得了数组的首元素的指针(地址),可以访问和修改数组a[4]中的所有元素
与void sum(int *a, int n)等价
指针与const限定符: (减小函数的在主函数的权限等)
const修饰后,为常量,不可修改。
定义函数时
声明为从const的指针要在声明时就初始化,若为定义的函数参数则由相应主函数的实参初始化。
可变指针指向可变(可修改的)数据:
void sum(char *p,int n)//权限最大,无const约束指针或类型数据(char).
可变指针指向常量(不可修改的)数据:
void sum(const char * p, int n)//应从右往左理解const char * p,p是一个指针,指向字符常量,可进行指针运算。
常量指针指向可变数据:
void sum (char const * p, int n)//从右往左char const * p,p是一个指针常量,指向字符型数据,可改变指针指向数据的值。
常量指针指向常量数据:
void sum(const char const * p,int n)//权限最小,指针(地址)的值不能改变,其指向的数据也不能改变
指针数组:是一个数组,数组元素时一个个指针变量
char* arr[4];//一级字符指针数组
char** arr[4];//二级字符指针数组
如: #include <stdio.h>
int main() {
int a = 10;
int b = 20;
int c = 30;
int *arr[3] = { &a,&b,&c };//注意[ ]优先级比*高
int i = 0;
for(i=0; i<3; i++){
printf("%d\n", *arr[i]);
}
return 0;
}
#include <stdio.h>
int main() {
int a[5] = { 1,2,3,4,5 };
int b[] = { 1,3,5,7,89 };
int c[] = { 2,3,4,5,6 };
int* arr[3] = { a,b,c };//先是一个数组,数组中的元素为int*,即整型指针。
int i = 0;
for (i = 0; i < 3; i++) {
int j = 0;
for (j = 0; j < 5; j++) {
//printf("%d ", *(arr[i] + j));//arr[i]是指针数组第一个元素储存的地址即a数组首元素的地址,(arr[i]+j)是a数组中第j个元素的地址
printf("%d ", arr[i][j]);//等价于二维数组
}
printf("\n");
}
return 0;
}
数组指针:
如: #include <stdio.h>
int main() {
int a = 10;
int* pa = &a;//整型指针
char ch = 'w';
char* pch = &ch;//字符指针
int arr[10] = { 0 };
int (*parr)[10] = &arr;//取出的是数组arr的地址,不是数组首元素的地址。先是是一个指针,再指向具有10个元素的数组,数组中的元素为int。
double* b[5];//指针数组
double* (*p)[5] = &b;//数组指针,即指向指针数组的指针。先是一个指针,再指向具有5个元素的数组,数组中的元素为double*。
return 0;
}
(&数组名)与数组名(首元素的地址)区别:
#include <stdio.h>
int main() {
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", &arr);//输出arr与&arr地址的值相同,意义不同(类型不同)。
//
int* pa = arr;//类型为int
int(*pb)[10] = &arr;//类型为int [10]或int []。
printf("%p\n", pa);//指针加一跳过指向对象的大小。
printf("%p\n", pa+1);//指针加一跳过一个int大小,为4字节。
printf("%p\n", pb);
printf("%p\n", pb+1);//指针加一跳过一个int [10]大小,为40字节。
return 0;
}
数组名是首元素的地址,但有两个例外:
1:sizeof(数组名) - 数组名表示整个数组,计算的是整个数组大小,单位是字节。
2:&数组名 - 数组名表示整个数组,取出的是整个数组的地址。
数组指针的使用:数组指针中存放数组的地址
如:用于一维数组,一般不使用数组指针,使用一级指针就可以了,使用数组指针反而麻烦。
#include <stdio.h>
int main() {
int arr[5] = { 1,2,3,4,5 };
int(*pa)[5] = &arr;//指向含有五个整型元素数组的指针,储存的是整个数组的地址
for (int i = 0; i < 5; i++) {
printf("%d " ,*((*pa) + i));
//首先*pa是数组arr,而数组arr是数组首元素的地址,故*pa是数组首元素的地址
//接着*pa+j是数组中下标为j的元素的地址,*((*pa)+j)是下标为j的元素
}
return 0;
}
用于二维数组
#include <stdio.h>
//void print(int arr[3][4], int m, int n) {//常规写法
// for (int i = 0; i < m; i++) {
// for (int j = 0; j < n; j++) {
// printf("%d ", arr[i][j]);
// }
// printf("\n");
// }
void print2(int(*pa)[4],int m, int n) {//定义了指向一维数组的指针,
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
printf("%d ", *(*(pa + i) + j));
//*(pa+i)首先是二维数组第i-1行的地址,*(pa+i)+j是第i-1行的第j-1个元素的地址
//*(*(pa+i)+j)是第i-1行的第j-1个元素
}
printf("\n");
}
}
int main() {
int arr[3][4] = { {1,2,3,0},{4,5,6,0},{7,8,9,0} };
//print1(arr, 3, 4);
print2(arr, 3, 4);
return 0;
}
int arr[5];//一维整型数组
int a[3][4];//二维整型数组
int* parr1[10];//指针数组,该指针指向存放10个整型指针的数组
int(*parr2)[10];//数组指针,指向存放10个整型元素的数组
int(*parr3[10])[5];//存放10个数组指针的数组,每个数组指针指向存放5个整型变量的数组
函数参数传递方式:按值传递和按引用传递
C中均为按值传递:被调函数的形参为主调函数的副本(拷贝),一般不影响主调函数变量的值。当传入指针时(类似按引用传递),主调函数
向被调函数传入了地址,可以改变主调函数中的相应变量的值。
如:void swap(int *p, int *q)//函数定义
int a=5, b=6;
swap(&a, &b);//函数调用
数组参数与指针参数
一维数组传参:
#include <stdio.h>
void test1(int arr[10]) {//1一维数组接收,可以
}
void test1(int arr[]) {//1一维数组接收,可以
}
void test1(int* arr) {//1一级指针接收可以
}
void test2(int* pa[10]) {//2一维指针数组接收,可以
}
void test2(int* pa[]) {//2一维指针数组接收,可以
}
void test2(int** pa) {//2可以
}
int main() {
int arr1[10] = {0};
int* arr2[20] = {0};
test1(arr1);//传的是首元素的地址,此指第一个元素int的地址
test2(arr2);//此指第一个元素int*的地址,即是二级指针
return 0;
}
二维数组传参:
#include <stdio.h>
void test3(int arr[3][4]) {//3二维数组接收,可以
}
void test3(int arr[][4]) {//3二维数组接收,可以
}
void test3(int arr[][]) {//3二维数组接收,列元素必须写,不可以
}
void test3(int* arr) {//一级指针接收,类型不匹配,不可以
}
void test3(int* arr[4]) {//一维指针数组接收,类型不匹配,不可以
}
void test3(int(*pa)[4]) {//一个数组指针接收,可以
}
void test3(int** arr) {//二级指针接收,与一维数组的地址不匹配,不可以
}
int main() {
int arr[3][4] = { 0 };
test3(arr);//传的是二维数组的首元素的地址,此指第一个元素一维数组的地址
return 0;
}
函数指针
指针函数
****************************************************************************************************************************************************************************************************************
结构体(构造类型)
struct关键字用于结构体定义。
结构是同一名字下的一组相关变量的集合。可以包含不同的数据类型。(当然包括结构类型)
结构体定义(声明)形式:
struct 结构标识符{ 如:struct student{
数据类型 成员1; char name[20];
数据类型 成员2; int age;
...... double mathscore;
};
};
定义之后,struct student便成为了自己定义的新的一种数据类型,与char、int、float、double使用基本相同。编译器不对数据类型分配内存,
当定义了相应类型的变量时编译器会为该类型的变量分配内存。自定义结构类型也是这样。
typedef关键字:为任意数据类型起一个别名,便于使用和理解。
关于结构体使用:
如:typedef struct student STU,即STU成为struct student 数据类型的另一个名字。
或如:typedef struct student{
char name[20];
int age;
double mathscore;
}STU;此STU不是struct student类型的结构变量,而是struct student 数据类型的另一个名字。
结构变量的声明(定义):
a.先声明结构类型,在定义结构变量。
如:struct student a;
b.定义结构类型的同时,定义结构变量。(此时结构标识符可以省略,且只能在此处定义结构变量,少使用)
如:struct student{
char name[20];
int age;
double mathscore;
}a;
结构变量的内存用sizeof运算符计算出,一般大于结构成员所占字节的和。
结构类型可以作为另一个结构的成员:
struct date{
int year;
int month;
int day;
}
struct student{
char name[20];
int age;
double mathscore;
struct date born;
};
结构变量的初始化:
与普通变量一样,不同成员用逗号隔开,字符串用双引号,单个字符用单引号。
如:struct student a = { "weihe", 20, 90};
结构成员的访问方法:C规定结构变量不能整体进行输入输出比较等操作,只能对具体的成员进行输入输出比较等操作。
相同类型的结构变量可以直接进行赋值操作,等价于结构变量成员依次进行赋值操作。
使用成员选择运算符(圆点运算符):
struct date{
int year;
int month;
int day;
}
struct student{
char name[20];
int age;
double mathscore;
struct date born;
};
struct student a = { "weihe", 20, 90, {2000,4,24}};
a.name == "weihe";//成员引用
a.age == 20;
a.date.year == 2000;//嵌套结构体的级联引用
结构数组:数组中元素均为结构类型。(定义和初始化)与普通数组相同。
结构指针:指向结构的指针。
成员访问运算符对于指针:圆点运算符和箭头运算符
如:struct student{
char name[20];
int age;
double mathscore;
};
struct student a[20]={ {"weihe",20,90},
{"齐天大圣",1000,95}};//初始化,未初始化的赋值为0。
struct student b;
a[4].age = 20;//对结构数组第5个元素中的一个变量赋值(引用)。
struct student *p = NULL;//定义结构指针。
p = &b;//得到结构类型b的地址用用取地址运算符。
p = a;//数组名就是数组的地址故不需要取地址运算符,等价于 p = &a[0];.
(*p).mathscore = 89;//同时使用解引用运算符与圆点运算符,等价于a[0].mathscore = 89;
*(p+1).mathscore = 79;//同时使用解引用运算符与圆点运算符,等价于a[1].mathscore = 79;,指针+1则跳过相应类型的一个元素,再此为一个结构元素。
p->mathscore = 99;//箭头运算符,等价于 a[0].mathscore = 99;
当p定义为指向结构类型的指针时就只能指向定义的类型了,不能指向结构类型中的成员,
但可以通过成员访问运算符(圆点或箭头)访问成员。对于结构指针有两种(圆点和箭头),对于结构名只有圆点运算符。
结构与函数:
struct student{
char name[20];
int age;
double mathscore;
};
void max(int n, struct student a);//结构作为函数的参数,是按值传递,在max函数新建一个结构体(形参),不影响main函数里传参的的结构体(实参)。不常用。
void max(int n,struct student *a);//结构指针作函数参数,传入的是main函数里实参(再此为结构体)的地址,影响实参数值。常用。
void max(int n, struct student a[]);//结构数组作函数参数,与结构指针作用相同,传入的是数组的首元素字节的首地址。
struct student max(int n);//结构作函数返回值,max函数最终返回一个结构(其中一般包含多个结构成员,可一次性返回),类似返回其他类型数据。
联合:C语言中多个不同变量共享同一内存区的功能。
关键字:union
定义:(类比结构)
union student {//该联合的三个成员共享一个声明联合的内存单元,须有,同一时间内有且只有一个成员有意义。不能为联合的所有成员都初始化,只能用相应类型的常量为第一个成员初始化。
int n;//4字节
double a;//8字节
char ch;//1字节
}u;//定义的同时可以声明一个联合u。
union student u1;//也可在定义之后再声明一个联合u1。
在为声明的一个联合u分配内存大小时,编译器按成员中最长的类型double分配储存单元。
联合指针:
union student {
int n;//4字节
double a;//8字节
char ch;//1字节
}u,u1;
union student *p = NULL;
p = &u;
u1.n = p->n;//p->n等价于*p.n。 类比结构。圆点运算符与箭头运算符。
联合与结构:结构可以是联合的成员,联合也可以是结构的成员,能互相嵌套。
如: struct yes{//有对象问名字与年龄
name[20];
int age;
};
struct no {//无对象问希望什么时候找与喜欢类型
int hopeseektime;
char liketype[50];
};
union otherhalf{定义一个联合是否有对象,成员为两个结构分别表示有或没有
struct yes a;
struct no b;
};
struct student {//定义学生的结构,包括姓名、年龄与有无对象。
char name[20];
int age;
union otherhalf;
};
枚举:一种值由程序员列出的类型,而且程序员必须为每个值命名(枚举常量)。
关键字:enum(类比结构)
在枚举类型声明语句中,花括号内的标识符都是整型常量,称为枚举常量。一般第一个枚举常量是0,依次增加1。
如: enum weekday{
Sun;//0
Mon;//1
Tue;
Wed;
Thu;
Fri;
Sat;//6......
}someday0;//定义枚举类型同时声明枚举变量someday0。
enum weekday someday;//先定义枚举类型,再声明枚举变量someday
someday = Mon;//枚举变量someday被定义的枚举常量赋值。
time()和localtime()
****************************************************************************************************************************************************************************************************************
int a[10];//数组定义
void array(int a[]) 等价于void array(int *a)//函数定义
动态内存分配
void *malloc(unsigned size)//函数原型
int *p = NULL;
p = (int*)malloc(sizeof(int));//使用,按字节分配内存,用于数组个数不定时
动态内存分配之后的释放函数
void free(void *p)//函数原型
int *p = NULL;
p = (int*)malloc(sizeof(int));
free(p);//使用
****************************************************************************************************************************************************************************************************************
void *calloc(unsigned n,unsigned size)//函数原型
为n个数组分配size字节大小的内存,并均初始化为0
****************************************************************************************************************************************************************************************************************
void realloc(void* p,unsigned size);//函数原型
重新为指针p所指的内存块分配适合的内存
若指针p指向NULL,则作用与malloc()相同
****************************************************************************************************************************************************************************************************************
EOF(End OF File)
表示文件结束符,用于while循环中,如while(scanf("%d",&n) != EOF),当在控制台输入ctrl+z时结束程序进行
****************************************************************************************************************************************************************************************************************
数据的存储
程序的版本:Debug版本和Release版本
Debug版本即调试版本,包含调试信息,利于去调试程序,无优化。
Release版本即发布版本,对程序进行了多种优化如代码大小和运行速度,便于使用,无调试信息。
数据类型:
类型的意义:确定开辟空间的大小与看待内存空间的视角。
基本类型:
char 字符型//char C语言没有规定是signed char 还是unsigned char 类型,由具体的编译器决定,多数编译器默认为 signed char类型
short 短整型
int 整型//int C语言规定为signed int类型
long 长整型
long long 更长的整型
float 单精度浮点型
double 双精度浮点型
整形家族:
char
unsigned char 0~255(2^8-1)
signed char -128~127
short
unsigned short int
signed short int
int
unsigned int 0~4,294,967,295(2^32-1)
signed int(默认) -2,147,483,648~2,147,483,647
long
unsigned long int
signed long int
浮点家族:
float
double
构造类型(自定义类型):
数组类型 a[]
结构体类型 struct
枚举类型 enum
联合类型 union
指针类型:
int *p
float *p
double *p
char *p
void *p
空类型:函数返回,函数参数,指针
数据的截断与整型提升(指不同的数据类型之间 ):
如:int a = 10;
char ch = 10;//10的二进制:00000000 00000000 00000000 00001010 最高位为0,原码反码补码相同
10为整型,占内存的四个字节,由字符类型(一个字节)ch储存时发生截断,只保留最后一个字节(00001010)储存。
ch转化为整型时从前补24个最高位即0,得补码00000000 00000000 00000000 00001010,最高位为0原码反码补码相同,即得原码00000000 00000000 00000000 00001010即10。
堆栈
局部变量储存在栈区,栈区地址从下到上逐渐增大,即上为高地址,下为低地址。栈区先使用高地址,在使用低地址
整型在内存中的储存:
已知数据在内存中以二进制储存,
有符号数的二进制表示方式有三种:原码,反码,补码。,每一种均分为符号位和数值位。
对于正整数:原码,反码,补码三者相同。
对于负整数:
数据储存时的二进制序列最高位为1,而正整数二进制序列最高位为0。
对于signed前缀类型,最高位为符号位无数值表示,而unsigned前缀类型最高位为数值表示。
原码:由数据的数值直接写出的二进制序列,补码取反再加一又得到原码或补码减去1再取反得到原码。
反码:原码符号位(即1)不变,其它位按位取反。
补码:先取反码再+1得到。
如 int a = -10;
内存中存放(16进制): F6 FF FF FF(此储存为小端字节序)
原码:10000000 00000000 00000000 00001010(四个字节)
反码:11111111 11111111 11111111 11110101
补码:11111111 11111111 11111111 11110110
16进制:FF FF FF F6
int b = 10
内存中存放(16进制):0a 00 00 00(此储存为小端字节序)
原码:00000000 00000000 00000000 00001010
反码:00000000 00000000 00000000 00001010
补码:00000000 00000000 00000000 00001010
16进制:00 00 00 0a
整型在内存中以补码的形式储存,表示和计算,原因是可以将符号位和数值域统一处理,加法和减法可以统一处理(cpu中只有加法器),原码与补码相互转换运算过程相同,不需要额外硬件电路。
数据数值的二进制的储存形式:大端字节序和小端字节序
如 int a = 0x11223344;//(16进制表示)其中11为高位,44为低位,类比十进制数字
大端字节序:把数据的低位字节序的内容存放在高地址处,高位字节序的内容存放在低地址处。
低地址 11 22 33 44 高地址
小端字节序:把数据的地位字节序的内容存放在低地址处,高位字节序的内容存放在高地址处。
低地址 44 33 22 11 高地址
想知道自己计算机数据的储存形式,可以采用如下代码简单判断:
用字符指针(只看一个字节)指向整型a的第一个字节,若为小端则为(16进制)01 00 00 00,若为大端则为 00 00 00 01。
#include <stdio.h>
int main(){
int a=1;
char *p=(char*)&a;
if(*p==1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
浮点型在内存中的存储:float(四字节),double(八字节),long double
浮点数在内存中以补码的形式储存。
C语言用二进制储存浮点数的标准:IEEE 754。
任意一个二进制浮点数V均可以化为一个形式:
(-1)^S*M*2^E 其中 ^ 表示指数运算。
S表示符号位,S为0时V为正数,S为1时V为负数。
M表示有效数字,范围大于等于1,小于2。
2^E表示指数位。
例如:9.0(十进制)->1001.0(二进制)->1.001*2^3->0 10000010 00100000000000000000000
5.5(十进制)->101.1(二进制)->1.011*2^2-> 0 10000001 01100000000000000000000
对于double类型:0 00000000000 0000000000000000000000000000000000000000000000000000 首位为符号位,接下来11位存放指数,余下52位存放小数部分。
对于float而言共四个字节32位:0 00000000 00000000000000000000000 首位1或0表示符号位,接着8位存放E即指数(二进制形式),余下的23位储存M(又称小数部分),M不够就补0.
对于1.XXXXXXXXX的二进制有效数字,实际储存时舍去1和小数点,只保留小数点后的0和1,等到取出已经储存好的浮点数时,M部分取出时在开头添加上1和小数点。
对于E指数,看做无符号整型(unsigned int),float中占8位,范围为0~255,考虑到指数E可能为负数,实际储存时要存入正数就需要修正,为指数E加上一个中间值,
127(float)或1023(double)。实际储存的是(E+127)的二进制形式,double占11位范围为0~2047,实际储存的是(E+1023)的二进制形式。
取出时:
E全为0时:
这时浮点数的指数E等于(1-127)或(1-1023)作为真实值,有效数字M不在加上第一位的1,而是还原为0.xxxxxx的小数,以此表示正负0或接近于0很小的数。
00000000八个位为0 ->实际的E的值为0 - 127 == -127,2^(-127)是很小的一个数,正负接近于0。
E全为1时:
这时,如果有效数字M全为0(还原之后为1.000.....),表示无穷大,符号确定正负。
11111111八个位为1 -> 实际的值为255 - 127 == 128,2^(128)是很大的数字 ,表示无穷大。
E不为全0或全1时:(最多)
指数E二进制的计算值减去127(或1023)得到指数位真实值,有效数字M前加上第一位的1,
5.5(十进制)->101.1(二进制)->1.011*2^2-> 0 10000001 01100000000000000000000
***************************************************************************************************************************************************************************************************************