1.C语言之父:Ken 汤普森。也是Go语言的发明者
2.程序出现的错误,称为bug(虫子形状)
1.增量编写法:写一点,及时验证
2.调试:遇到问题,用调试去定位bug
①步过:当前函数,一步一步向下走
②步入:进入该行函数的内部
Ctrl+Alt+L
:代码格式化快捷键,一键标准化你的代码,专治强迫症
crtl + 鼠标左键
:快速到函数定义位置
基础数据类型:①整型 ②浮点型 ③字符型
2.常量
①整型 int
②实型(浮点型) float、double
③字符型
④字符串型
3.变量
(1)变量的命名规则:
只能包含字母、下划线、数字,且只能以字母或下划线开头。(不能包含特殊符号,不能以数字开头)
避免命名的变量名与关键字重名
4.符号常量
#define N 5
//符号常量
#include
#define PI 3+2 //注意 define后面不加分号
int main() {
int i = PI*2; //这里PI是直接替换为 3+2 。 i = 3+2*2 = 7
printf("i=%d\n",i);
return 0;
}
强制类型转换
int i = 5;
float f = (float)i/2;
printf格式控制
%-3d
:长度为3,左对齐(负号)
%5.2f
:长度为5,小数点后保留2位
%d:整数
%f:浮点数
%c:字符型
%s:字符串
%o:八进制
%x:十六进制
10转2:短除法,除2。从最底下开始为高位
10转8:短除法,除8。从最地下开始为高位
#include
int main() {
//int i = 123;
//int i = 0173 // 八进制
int i = 0x7b; //十六进制
printf("%d\n",i);
printf("%o\n",i);//八进制
printf("%x\n",i);//十六进制
return 0;
}
A 65 0x41
B 66 0x42
C 67 0x43
D 68 0x44
①标准输入缓冲区,是一段内存。大小为4096B = 4KB
②scanf卡住(阻塞),当且仅当标准输入缓冲区为空时。
scanf是 行缓冲。输入值后必须回车,才能触发scanf。标准输入缓冲区内是输入的字符串,外加一个\n
③清空标准输入缓冲区
fflush(stdin);
④scanf也有返回值。ret是scanf匹配成功的个数,方便单步调试。
⑤scanf多种数据混合输入(混合读取),注意 %c前面必须加空格,%s不加取地址
注意,【输出%d %c时,前面要用空格间隔开。否则%c会读取输入的空格。因为%c不能忽略空格和\n】
printf("%d %c",i,c);//正确,%d %c用控制间隔开
printf("%d%c",i,c);//错误,输入%c会读入你敲的空格(为了分词的空格)
①算数运算符 + - * / %
②关系运算符 > < == >= <= !=
③逻辑运算符 ! && ||
④指针运算符 *,&
⑤成员选择运算符 . ->
*
:①乘号 ②指针 ③解引用
记住优先级的目的:不加多余的括号(过多括号,降低代码阅读速度,看起来也很不专业)
1.算数运算符和算数表达式
2.关系运算符和关系表达式
①关系表达式的值只有真和假,真为1,假为0。(C语言中没有布尔类型…)
②优先级:算数运算符 > 关系运算符
3.逻辑运算符和逻辑表达式
①逻辑表达式也只有真和假,真为1,假为0
②优先级:逻辑非 > 算数运算符 > 关系运算符 > 逻辑与、逻辑或
③短路运算:
&&:当i为假时,不会执行逻辑与后面的表达式
||:当i为真时,不会执行逻辑与后面的表达式
称为短路运算
#include
int main(){
int i = 0;
i&&printf("you can't see me!\n");//与运算,前面为假,则整体为假,短路后面的运算
i||printf("you can see me!\n"); //或运算,前面为真,则整体为真,短路后面的运算
return 0;
}
int i = 0;
i&&printf("you can't see me!");
i为真,输出后面的。i为假,短路后面的。
这种写法等价于下面。短路运算,缩短了代码长度。
int i = 0;
if(i){
printf("you can't see me!");
}
4.赋值运算符
①左值 L-value:可以放在赋值运算符左边的值,表达式不能作为左值,只能放变量
右值 R-value:可以放在赋值运算符右边的值
②复合赋值运算符 : +=、-=、*=、/=
(复合赋值运算符,可以提高编译速度)
5.求字节运算符 sizeof
sizeof运算符的作用:求常量或变量所占的空间大小
1.while循环
while循环体里,一定要有使得while判断表达式趋近于假的操作(如i++),否则会死循环
2.for循环
for(表达式1;表达式2;表达式3){ 循环体 }
表达式1,一般用来初始化。可以初始化多个变量。
表达式2,是循环判断语句,相当于while括号里的内容。
表达式3,是while中容易漏掉的,让循环判断语句趋近于假的操作。
3.continue语句
跳过本次循环,进入下一轮循环。
4.break语句
跳出并提前结束整个循环
OJ-4.3:换钞票
思路:暴力枚举
#include
//某人想将手中的一张面值100元的人民币换成10元、5元、2元和1元面值的票子。
// 要求换正好40张,且每种票子至少一张。问:有几种换法?
int main(){
int method = 0;
int R10,R5,R2,R1;
for(int R10 = 1; R10 <= 10; ++R10){ //100元最多换10张10元
for(int R5 = 1; R5 <= 20; ++R5){//100元最多换20张5元
for(int R2 = 1; R2 <= 40;++R2){
for(int R1 = 1; R1 <= 40; ++R1){
if(R1*1+R2*2+R5*5+R10*10==100 && R10+R5+R2+R1==40){
//printf("%d %d %d %d\n",R10,R5,R2,R1);
method++;
}
}
}
}
}
printf("%d\n",method);
return 0;
}
1.数组使用场景:
①具有相同的数据类型
②需要保存原始数据
2.数组是构造类型,数组名保存的是数组的起始地址,对应保存的是数组的首元素。
3.声明数组长度的时候不能用变量。即使某些IDE不报错,OJ可能也会报错。尽量使用常量定义数组长度,选题目声明的最大范围即可,稍微浪费一些空间。
①若要申请静态数组,则长度必须为常量。不可以是变量n。int A[10];
②若数组长度是变量n,则必须申请动态数组。
//C语言
int *A = (int *)malloc(n*sizeof(int)); //申请动态数组A
//C++
int *A = new int[n];
(1)声明/定义
int a[10]; //a[0] ~ a[9],共10个
(2)初始化
①全部初始化
int a[10]={1,2,3,4,5,6,7,8,9,10};
②部分部分初始化,未写到的部分,值为0
int a[10] = {0};
③不指定长度,由初始化的个数确定数组长度
int a[ ] = {1,2,3,4,5}; //长度为5
访问越界的危险之处:i,j没有重新赋值,却在访问越界时被迫改变了值
#include
int main(){
int a[5]= {1,2,3,4,5,6};
int i = 10;
int j = 20;
a[6] = 6,a[7] = 7;//访问越界
printf("i=%d,j=%d\n",i,j);
return 0;
}
1.【数组传递给子函数时,长度无法传过去,只能传递数组的首地址。若想要传递数组长度,需要再用一个参数(int变量)保存数组的长度】
数组的长度无法直接传递到子函数,需要额外再用一个int len 变量保存数组长度,双参数传递。
因为传递到子函数后,数组就被弱化成了指针(8字节),实际上传递到子函数的是数组的起始地址
2.在子函数中可以修改数组元素的值,主函数里能接收到。因为操作的是内存地址。
#include
void print(int a[],int len){ //注意这里写的是 int a[],传递到子函数里的是 数组的起始地址
for(int i = 0; i < len; ++i){
printf("%d ",a[i]);
}
}
int main(){
int a[5]= {1,2,3,4,5};
print(a,sizeof(a)/sizeof(int));
return 0;
}
1.初始化:双引号内放字符串
char c[10] = "Iamhappy";
字符数组只有碰到结束符\0
,即0x00才会停止输出。正常情况下给字符数组赋值,一定要留一个位置放\0
。否则 %s可能会多输出几个乱码。
#include
int main(){
char c[10] = "I am happy";
c[10] = 0x41;
c[11] = 0x42;
c[12] = 0x43;
c[13] = 0x44;
printf("%s\n",c);
return 0;
}
3.scanf读取字符数组
读入时,会自动往字符数组中放\0
。
(scanf会忽略空格和\n)
1.gets(字符数组名c)
:读取一整行
或者fgets(c,sizeof(c),stdin)
fgets是从标准输入stdin中读取 若干个(第二个参数为读取长度)到p所指空间中,是安全的。
而gets是不安全的,可能访问越界,没有限制长度。读取内容可能会超出p本身的空间大小
2.puts(字符数组名c)
:输出字符数组内容
等价于printf("%s\n",c);
例:wangdaoOJ 8.2
#include
#include
void func(char *&p){
p = (char *)malloc(100);
fgets(p,100,stdin);
}
int main() {
char *p; //字符指针p
func(p);
puts(p);
free(p);
return 0;
}
3.str系列函数
①strlen(c)
:求字符数组长度
②strcat(c,d)
:把后一个字符数组内容拼接到前一个中。
③strcpy(a,b)
:把后一个内容,完全复制给前一个
④strcmp(e,"how")
:<,返回负值;=,返回0;>,返回正值。
例题1:王道OJ 5-2
读取一个字符串,字符串可能含有空格,将字符串逆转,原来的字符串与逆转后字符串相同,输出0,原字符串小于逆转后字符串输出-1,大于逆转后字符串输出1。例如输入 hello,逆转后的字符串为 olleh,因为hello 小于 olleh,所以输出-1
#include
#include
int main() {
char c[100], d[100]={0};//初始化,防止反转后没有\0
gets(c);
int len = strlen(c);
for (int i = 0; i < len; ++i) {//字符数组逆置
d[i] = c[len-1-i];
}
// printf("%s\n%s\n", c, d);
int res = strcmp(c,d);
if(res<0) printf("%d\n",-1);
if(res==0) printf("%d\n",0);
if(res>0) printf("%d\n",1);
return 0;
}
注意使用 “增量编写法”,随时检查已写代码是否正确,方便快速定位问题。
指针大小:32位系统为4字节,64位系统为8字节。(多少位系统就是地址是多少位,指针就是多少位。)
1.指针的定义、初始化
基本类型名 *指针名;
int i = 5;
int *i_pointer = &i; //指针变量的初始化,是某个变量的地址,不能随意赋值
int *a,*b,*c;//定义指针变量,*和后面的指针变量名挨着
指针就是地址。指针变量就是用来保存地址的变量。
2.取地址、取值操作符,指针的本质
&:取地址操作符,引用
*:取值操作符,解引用
3.直接访问、间接访问
int i = 5;
int *i_pointer; //定义
i_pointer = &i; //初始化
printf("%d\n",i); //直接访问
printf("%d\n",*i_pointer); //间接访问
C语言的函数调用是值传递,实参赋值给形参
想要在子函数中能改变main函数中的值:
方法一:使用指针传递:实参传地址,形参和子函数内解引用
方法二:C++引用
两者的效果都是,使得子函数内的形参变量,和main函数内的实参变量,的地址相同。操纵同一块内存空间。
例题1:6-1
用子函数改变main函数中函数的值,C语言实现
#include
int change(int* j){ //形参j现在是指针类型,即地址。(变量地址,弱化为指针)
*j /= 2;
return *j;
}
int main(){
int i;
while(scanf("%d",&i) != EOF){
change(&i);//C语言只能值传递,把变量的地址传给子函数
printf("%d\n",i);
}
return 0;
}
例2:数组是引用传递,函数中的int array[ ]会直接改变主函数中数组的值。int array[ ] 等价于 int *array,传递的是数组的首地址
#include
void changeArray(int array[]){
for(int i = 0; i < 5; ++i){
array[i] = i;
}
}
void printArray(int array[]){
for(int i = 0; i < 5; ++i){
printf("%d ",array[i]);
}
printf("\n");
}
int main() {
int array[5];
changeArray(array);
printArray(array);
return 0;
}
栈空间:函数结束,操作系统立刻回收栈空间
堆空间:进程结束,堆空间才消亡
5.指针的偏移
即指针(地址)的加减。
加,往后偏;减,往前偏。
*(a+i) //等价于 a[i]
a[i] 等价于 *(a+i)
char d[ ] 等价于 char *d
①malloc头文件:
②malloc返回值为 void *,空指针类型。但空指针类型不能偏移(加减),无意义。所以要强转为我们定义的类型指针。
③使用malloc申请堆空间,就要使用free释放申请的堆空间。
free释放的地址,必须是当时malloc申请的地址。若要进行偏移,则用 *q 保存 *p,p进行偏移,q进行释放——free(q)。
#include
#include //malloc头文件
int main() {
char *p;
int size;
scanf("%d",&size);
p = (char *)malloc(size);//使用malloc动态申请堆空间
p[0]='H';
p[1]='o';
p[2]='w';
p[3]='\0';
puts(p);
free(p);//使用free释放堆空间。释放的地址必须是malloc申请的地址,不能修改。
return 0;
}
例题6-2:使用malloc动态申请变长字符数组
#include
#include
int main(){
int size;
scanf("%d",&size);
char *p = (char *)malloc(size); //使用malloc
getchar();//吞掉回车:清除标准输入缓冲区中的\n
gets(p); //输入字符串
puts(p); //输出字符串
return 0;
}
①子函数的作用:将某个常用功能封装起来,下次可以直接调用
②子函数的局限:无法将main函数中变量的值传递回去,只能传递指针或者使用C++引用(&)
想要在子函数中能改变main函数中的值:
方法一:使用指针传递:实参传地址,形参和子函数内解引用
方法二:C++引用
两者的效果都是,使得子函数内的形参变量,和main函数内的实参变量,的地址相同。操纵同一块内存空间。
①缩小问题规模的return
②递归出口
例题1:跳台阶问题:一次只能跳1阶或者2阶,问到n阶共有多少种跳法?
#include
int fibonacci(int n){
if(n==1 || n==2){
return n;
}else{
return fibonacci(n-1)+fibonacci(n-2);
}
}
int main(){
int n;
while(scanf("%d",&n) != EOF){
printf("%d\n",fibonacci(n));
}
return 0;
}
①局部变量的生命周期:离自己最近的大括号内有效
②全局变量的生命周期:从定义位置到该文件末尾都有效。建议少用全局变量
③不同函数内部可以使用同名的局部变量,互不干扰。
静态局部变量只会初始化一次,仅在函数内部有效。
全局变量也只会初始化一次,在整个.cpp文件内都有效,全局有效
1.结构体定义
①结构体可以嵌套定义,即在结构体里定义结构体
2.结构体初始化
①加struct,使用大括号,按照定义顺序进行初始化
②调用,用对象名.属性名
方式
4.结构体对齐(计算结构体的大小)
①结构体的大小,必须是其最大成员的整数倍
②为什么要对齐:为了CPU能高效地取走内存上的数据
③各种数据类型所占内存空间:
char:1字节
short:2字节
int、float:4字节
double:8字节
5.结构体指针
指针->成员
等价于 (*指针).成员
6.typedef起别名
①给结构体、结构体指针起别名
struct student{
int num;
char name[20];
char sex;
};
int main(){
struct student s = {1001,"wangdao",'M'}; //结构体对象
struct student *p = &s; //结构体指针
printf("%d %s %c\n",s.num,s.name,s.sex);
printf("%d %s %c\n",p->num,p->name,p->sex);
return 0;
}
typedef struct student{
int num;
char name[16];
char sex;
}stu,*pstu; //stu等价于struct student, pstu等价于struct student *
int main(){
stu s = {1001,"wangdao",'M'}; //结构体对象(stu等价于 struct stu)
pstu p = &s; //结构体指针 (pstu等价于 struct student * 等价于 stu *)
printf("%d %s %c\n",s.num,s.name,s.sex);
printf("%d %s %c\n",p->num,p->name,p->sex);
return 0;
}
②给标准数据类型起别名
typedef int INTERGER;
INTERGER i; //等价于 int i;
INTERGER j;
INTERGER k;
应用场景:i,j,k的数据类型可能经常要变动,从int变为short,或变为float。如果不用别名,则需要一个个修改,比较低效。若用typedef,则只需要修改typedef那一行代码。
1.对于普通变量和指针变量,都要加引用,才能在子函数中改变main函数中变量的值
#include
void reference(int *&p,int *q){ //引用&必须和变量名紧邻
p = q;
}
int main(){
int *p = NULL;
int i = 1;
int * q = &i;
printf("%x %x\n",p,q);
reference(p,q);
printf("%x %x\n",p,q);
return 0;
}
普通变量的引用,等价于(一级)指针
指针变量的引用,等价于二级指针
2.C++中才开始使用布尔类型,也可以进行加减乘除等数值运算
bool a = true; //值为1
bool b = false; //值为0