进制的换算
变量
定义变量:类型 + 标识符 = 数值
全局变量是定义在代码块之外的变量,局部变量则是定义在代码块之内的变量。
局部变量与全局变量的标识符相同时,局部变量优先,最好设置不同的标识符,避免不必要的bug。
局部变量作用范围是其所在的局部范围内,全局变量则作用在全部范围。
变量的作用域(scope):局部变量的作用域是变量所在的{}之内,全局变量的作用域是整个工程。
全局变量任何人都能使用或修改,不安全,不建议使用。
生命周期:局部变量的生命周期从进入作用域开始、离开作用域结束,全局变量的生命周期是整个工程的生命周期。
常量
字面常量
const修饰的常变量,使用const(常属性)将变量定义为常量,使其不可变。被修饰得常变量本质上还是变量,只是具备了常属性。‘const int a = 0;’
#define定义的标识符常量
枚举常量(枚举关键字enum)
enum SEX
{
MALE,//0
FEMALE,//1
NOTHING//2
//上面的数值默认从第一个为0开始,第二个为0,以此类推
};//这里的;需要注意,enum只是语句,需要在结尾加上;
int main()
{
enum SEX bjp = MALE;//将变量bjp定义为MALE
enum SEX sjy = FEMALE;//将变量sjy定义为FEMALE
return 0;
}
extern用来声明外部符号。
输入函数scanf(scanf是C语言的标准,scanf_s是vs编译器的标准)
取地址符号&(加在标识符前 ‘scanf(%d, &num1);’),定义变量时相内存申请的空间也就是其所在的地址,使用&可以在所存储的地址取出。
!!在VC/vcprojectitems/newc++file.cpp中,加入#define _CRT_SECURE_NO_WARNINGS 1。
字符串
int main()
{
char arr1[] = "123";//将"123"字符串存放在名为arr1的数组当中
//字符串的末尾会默认带有\0,\0是字符串的结束标志
//即"123" == '1','2','3','\0'
char arr2[] = {'1','2','3','\0'};//将'1','2','3'字符存放在名为arr2的数组中
//以字符形式存放的时候要在末尾加上'\0'或者直接0
printf("%s\n", arr1);//打印arr1数组的字符串
printf("%s\n", arr2);//打印arr2数组的字符
printf("%d\n", strlen(arr1));//打印数组arr1的字符串长度
//strlen()'string length',strlen是计算字符串长度的库函数
return 0;
}
转义字符
\,转变其原有意思
\n,最常用的换行
注释
//
语句
while循环语句
#include <stdio.h>
int main()
{
int line = 0;
while(line < 2000)//表达式是循环的条件
{
line++;
printf("line = %d\n",line);
}
while(line = 2000)
{
printf("Game over\n");
}
return 0;
}
函数
包括库函数和自定义函数。
库函数例如scanf(),printf()是已定义的可直接调动的函数。
自定义函数是指我们可自定义创建一个函数,方便调用。格式为:'函数返回类型 函数名(函数参数)',例:
int Add(int x,int y)
{
int z = x + y;
return z;
}
int main()
{
int a = 0;
int b = 0;
int c = 0;
int num1 = 0;
int num2 = 0;
int sum1 = 0;
int sum2 = 0;
int sum3 = 0;
scanf("%d,%d,%d,%d,%d",&a,&b,&c,&num1,&num2);
sum1 = Add(a,b,c);
sum2 = Add(num1,num2);
sum3 = Add(a,b,c,num1.num2);
printf("sum1 = %d\nsum2 = %d\nsum3 = %d\n",sum1,sum2,sum3);
return 0;
}
数组
#include<stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};//定义一个名为arr的数组,可以存放10个整型数字
//存放着1-10,共十个数字
char ch[20];//定义一个名为ch的数组,可以存放20个字符
float arr2[30];//定义一个名为arr2的数组,可以存放30个单精度浮点型数字
printf("%d\n",arr[4]);//这里表示的是打印出arr数组里下标为4的数据
//打印出的结果是'5'
//利用下标就可以访问数组内的元素
int i = 0;
while(i<10)//利用循环语句将arr的数据全部打印出来
{
printf("%d\n",arr[i]);
i++;
}
return 0;
}
每个数组的数据存放在内存空间里,数据的下标默认从0开始。
例如,数组 arr[5] = {1,2,3,4,5};
在这个数组中,‘1’的下标是0,‘2’的下标是1,‘3’的下标是2,‘4’的下标是3,‘5’的下标是4。
操作符
算术操作符
+ , - , * , /(除) , %(求余)
移位操作符
<<() , >>
移的是2进制位
#include<stdio.h>
int main()
{
int a = 1;//将1赋值给a,存储在内存中名为a的空间,占4个字节,也就是32bit位
//00000000000000000000000000000001
a<<1;//就是将a的2进制序列整体向左移一个单位
//a的值就会变成
//00000000000000000000000000000010
//那么在换算成十进制数就是2(0*2^0+1*2^1 = 2)
return 0;
}
位操作符(2进制位操作符)
&(按位与),|(按位或),^(按位异或)
#include<stdio.h>
int main()
{
int a = 3;// 2进制:011
int b = 5;// 2进制:101
int c = a&b;//2进制:001
//与的计算:对应的2进制位有0则为0,都是1才为1
//按位置与,0&1、1&0、1&1,得到的结果就是001
printf("%d\n",c);//结果为1(2进制数001的十进制数)
int d = a|b;//2进制:111
//或的计算:有对应的2进制位有1则为1
//按位置或,0|1、1|0、1|1,结果为111
printf("%d\n",d);//结果为7(2进制数111的十进制数)
int e = a^b;//2进制:110
//异或的计算:对应的2进制位相同为0,反之为1
//按位置异或,0^1、1^0、1^1,结果为110
printf("%d\n",e);//结果为7(2进制数111的十进制数)
return 0;
}
赋值操作符
=, +=, -=, *=, /=, |=, ^=, <<=, >>=
#include<stdio.h>
int main()
{
int a = 0;
a = 20;//赋值
a += 10;//即,a = a + 10
a -= 10;//即,a = a - 10
a &= 10;//即,a = a & 10
a |= 10;//即,a = a | 10
a <<= 10;//即,a = a << 10
return 0;
}
单目操作符
!(逻辑反操作)、-(负值)、+(正值)、&(取地址)、sizeoff(计算变量或类型所占空间的大小,单位是字节)、~(对一个数的2进制数按位取反)、–(前置、后置–)、++(前置、后置++)、*(间接访问操作符)、(类型)强制转换类型
关系操作符
>、 >=、 <、 <=、 !=、 ==
逻辑操作符
&&,逻辑与
||,逻辑或
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
int c = 0;
int c2 =0;
int d = a && b;//a,b都为真,结果为真
int e = a && c;//a真c假,结果为假
int f = a && c2;//a,c2都为假,结果为假
//逻辑与有一个参数为假即为假
int g = a || b;//a,b都为真,结果为真
int h = a || c;//a真c2假,结果为真
int i = a || c2;//a,c2都为假,结果为假
//逻辑或有一个参数为真即为真
return 0;
}
条件操作符(三目操作符)
exp(表达式)
exp1 ? exp2 :exp3
#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
int max = 0;
max = (a > b ? a : b);//a > b为exp1,a为exp2,b为exp3
//exp1为真,则输出exp2;反之输出exp3
//a > b为假,所以输出b,即max = 20
printf("%d\n", max);
return 0;
}
逗号表达式
exp1,exp2,exp3,…,expn
关键字:auto、break、case、char、const、continue、default、
关键字static
static修饰局部变量,使其生命周期变长。
static修饰全局变量,改变其作用域,让静态的全局变量只能在其所在的源文件内使用,不能再其它源文件调用。
static修饰函数,改变其连接属性。
#include<stdio.h>
void test()
{
static int a = 1;//将a修饰成一个静态的局部变量
a++;
printf("a = %d\n", a);//结果为2 3 4 5 6
//如果不使用static修饰
//结果为2 2 2 2 2
}
int main()
{
int i = 0;
while (i < 5)
{
test();
i++;
}
return 0;
}
#define定义常量和宏
#include<stdio.h>
int Max(int x, int y)//定义函数Max
{
if (x > y)
return x;
else
return y;
}
#define MAX(X,Y)(X>Y?X:Y)//定义宏MAX
int main()
{
int a = 10;
int b = 20;
int max1 = Max(a, b);//使用函数Max比较a,b的大小
printf("max1 = %d\n", max1);
int max2 = MAX(a, b);//使用宏MAX比较a,b的大小
printf("max2 = %d\n", max2);
return 0;
}
内存
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。
“内存单元的编号 --> 地址 --> 指针”
变量是创建内存中的(在内存中分配空间的),每个内存单元都有地址,所以变量也是有地址的。
int main()
{
int a = 10;//申请整型a占四个字节的空间
//:00000000 00000000 00000000 00001010
//:0x 00 00 00 0a
//一个字节四个比特位
&a;//取a所占4个字节中第一个字节的地址
printf("%p",&a);//打印地址需要%p
}
指针
指针的本质是地址,就是用来存放地址的。
指针变量的大小取决于地址的大小:
32位机器中地址是32个bit位,即4个字节;
64位机器中地址是64个bit位,即8个字节。
局部指针变量未初始化,是野指针,默认为随机值。
规避野指针:
1.指针初始化;
2.小心指针越界;
3.指针指向空间释放;
4.避免返回局部变量的地址
5.指针使用前检查有效性
#include<stdio.h>
int main()
{
int a = 10;//4个字节的空间
printf("%p\n", &a);//结果008FF77C,十六进制
//&a取地址
//%p打印地址
int* p = &a;//变量p用来存放a的地址,也叫指针变量
//int
printf("%p\n", p);//结果也为008FF77C
*p = 20;//将地址p的值改为20
//* 解引用操作符
printf("%d\n", a);//结果20
printf("%d\n", sizeof(p));//计算指针p的长度,结果4
//int a = 10;//在内存中创建一个变量a的空间,存放着值10
//这个空间a的地址为008FF77C
//int *p = &a;//在内存中创建一个指针变量p的空间
//取地址&a的值008FF77C,存放
//*p = 20;//根据p中存放的地址信息,找到a,将a的值改为20
//字符类型
char ch = 'a';
char* pc = &ch;
printf("%p\n", pc);
*pc = 'b';
printf("%c\n", ch);
printf("%d\n", sizeof(pc));//计算指针pc的长度,结果4
return 0;
}
指针运算
指针±整数
指针减指针
计算指针之间相差的元素个数
两个指针相减的前提:指针指向的同一块连续空间。
int my_strlen(char* str)
{
//利用指针相减计算字符串长度
char* start = str;
while (*str)
{
str++;
}
return str - start;
}
int main()
{
char arr[]="abcdef";
int len = my_strlen(arr);
printf("%d\n", len);
}
关系运算
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
一级指针传参可以是&,也可以是指针变量,还可以是数组名。
二级指针传参可以是&一级指针,也可以是耳机指针变量,还可以是指针数组的数组名。
二级指针
int main()
{
int a = 10;
//&a 0x00baf7ec int*
int* p = &a;//一级指针变量
//0x00baf7ec int*
int** pp = &p;//二级指针变量
//0x00baf7e0 int**
return 0;
}
指针数组(存放指针的数组)
int main()
{
int arr1[5];//整型数组
char arr2[5];//字符数组
int* arr3[5];//存放整型指针的数组
char* arr4[5];//存放字符指针的数组
return 0;
}
数组指针
int main()
{
int arr[5];//
int* parr1[10];//指针数组
int(*parr2)[10];//数组指针
int(*parr3[10])[5];//数组指针
//parr3[10]是存放数组指针的数组
return 0;
}
二维数组指针
void print(int (*p)[5],int r,int c)
//arr传参过来相当于首元素地址,第一行五个元素的首地址
//(*p)[5]指二维数组arr第一行五个元素,视为一个一维数组
// p等于arr第一行的首地址
//p+i则为下一行
{
int i = 0;
for (i = 0;i < r;i++)
{
int j = 0;
for (j = 0;j < c;j++)
{
printf("%d ", *(*(p + i) + j));
//(p+i)==第i行的地址,*取得第i行
//(*(p+i)+j)==第i行第j个元素的地址,*取得该元素
}
printf("\n");
}
}
void test()
{
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6,},{3,4,5,6,7,} };
print(arr, 3, 5);
}
int main()
{
test();
return 0;
}
函数指针
int Add(int x, int y)
{
return x + y;
}
int main()
{
printf("%p\n", Add);//00EA10B4
printf("%p\n", &Add);//00EA10B4
//Add和&Add都是函数的地址,没有区别
int(*pf)(int x, int y) = &Add;
//pf即函数指针变量
int sum = (*pf)(3, 5);//8
int sum2 = (pf)(3, 5);//8
//语法上, pf=Add
printf("%d\n", sum);
return 0;
}
函数指针数组,把函数的地址存到一个数组中,那这个数组就叫函数指针数组。
例:int (*pfArr[5])(int,int) = {0,Add,Sub,Mul,Div};//转移表
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void menu()
{
printf("***************\n");
printf("**1.Add 2.Sub**\n");
printf("**3.Mul 4.Div**\n");
printf("**0.exit*******\n");
printf("***************\n");
}
int main()
{
//简单实现一个计算器加减乘除
int input = 0;
int x, y, ret = 0;
int (*pfArr[5])(int,int) = {0,Add,Sub,Mul,Div};
//转移表
//
do
{
menu();
printf("pick:>");
scanf("%d", &input);
if(input==0)
{
printf("exit\n");
break;
}
if (input >= 1 && input <= 4)
{
printf("输入两个数:>");
scanf("%d %d", &x, &y);
ret = pfArr[input](x, y);
printf("%d\n", ret);
}
else
printf("erro\n");
} while (input);
return 0;
}
回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
void*的指针可以接收任意类型的地址。
结构体
结构是一些值的集合,这些值称为成员变量,结构的每个成员可以是不同类型的变量。
结构体传参时,要传递结构体的地址。因为函数传参时,参数是需要压栈的,如果一个结构体对象过大,参数压栈的的系统开销比较大,所以会导致性能降低。
struct Stu
{
char name[20];
int age;
char sex[5];
int hight;
}s1,s2,s3;//全局变量
int main()
{
struct Stu s4;//局部变量
return 0;
}
#include<stdio.h>
#include<string.h>
//当一个对象具有多种属性
//例如,人有名字,年龄,性别,身份证号码等等
//一本书籍具有书名,作者,价格,出版社,类型等等
//可以创建一个结构体类型给这些复杂对象
struct Book
{
char name[20];
short price;
};
int main()
{
struct Book b1 = { "C语言程序设计", 55 };
//利用结构体类型创建一个该类型的结构体变量
printf("书名:%s\n", b1.name);
printf("价格:%d\n", b1.price);
//最基础的打印b1的书名和价格利用:变量.成员
struct Book* pb = &b1;
printf("书名:%s\n", (*pb).name);
printf("价格:%d\n", (*pb).price);
//利用pb打印出b1的书名和价格:结构体变量.成员
printf("书名:%s\n", pb->name);
printf("价格:%d\n", pb->price);
//利用pb打印出b1的书名和价格:结构体指针->成员
//最后打印出的结果都相同
b1.price = 15;//修改b1的值
printf("修改后的价格:%d\n", b1.price);
//strcpy()函数,字符串拷贝string copy
strcpy(b1.name, "C++");//将b1.name的值拷贝为C++
printf("%s\n", b1.name);
return 0;
}
#include<stdio.h>
//当一个对象具有多种属性
//例如,人有名字,年龄,性别,身份证号码等等
//一本书籍具有书名,作者,价格,出版社,类型等等
//可以创建一个结构体类型给这些复杂对象
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
};
int main()
{
struct Stu stu001 = { "孙某某", 23,"女","17xxxxxx21" };
struct Stu stu002 = { "白某某", 23,"男","17xxxxxx12" };
//利用结构体类型创建一个该类型的结构体变量
printf("姓名:%s 年龄:%d 性别:%s 学号:%s\n", stu001.name, stu001.age, stu001.sex, stu001.id);
printf("姓名:%s 年龄:%d 性别:%s 学号:%s\n", stu002.name, stu002.age, stu002.sex, stu002.id);
return 0;
}
程序环境
c的任何一种实现中,存在两种不同的环境:
第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
第2种是执行环境,它用于实际执行代码。
翻译环境
预编译(预处理),文本操作
编译,将C语言代码转换成汇编代码
汇编,将汇编指令转换为二进制,生成.o目标文件
链接,合并段表,符号标的合并和重定位
源文件->编译器->目标文件->链接器->可执行文件
链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人
的程序库,将其需要的函数也链接到程序中。
运行环境
程序执行的过程:
1.程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2.程序的执行便开始。接着便调用main函数。
3.开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
4.终止程序。正常终止main函数;也有可能是意外终止。
预处理
//预定义符号
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
#define 定义标识符
#define MAX 1000
#define reg register //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ , \
__DATE__,__TIME__ )
//注意:define定义标识符是,在最后最后不要加上‘;’,容易出问题
宏与函数相比,程序的规模更小、速度更快;
函数只能在类型合适的表达式上使用,宏是类型无关的。
另外,宏在每次调用时,一份宏定义的代码将插入到程序中,很可能会大幅增加程序长度;
宏无法调试;宏与类型无关,不够严谨;宏可能会带来运算符优先级的问题,容易出错。
int Max(int x, int y)
{
return(x > y ? x : y);
}
#define MAX(x,y)((x)>(y)?(x):(y))
int main()
{
int a = 10;
int b = 20;
int m1 = Max(a, b);//函数
printf("%d\n", m1);
int m2 = MAX(a, b);//宏
printf("%d\n", m2);
return 0;
}