目录
对于for循环各部分的作用理解更加深刻。
字符数组与字符串的区别在于末尾是否有"\0"。
指针的本质是地址,指针传递的本质是函数调用是值传递。
高效的栈空间中会消除结束调用的栈空间,而malloc申请的堆空间只能被free释放。
运算符分类
C语言提供了13种类型的运算符
- 算术运算符(+ - * / %)
- 乘、除、取余的优先级高于加、减
- %为取模运算符,返回值是余数而不是商
- 关系运算符(> < == >= <= !=)
- 返回值只有真和假,对应1和0
- 关系运算符优先级低于算术运算符
- while(scanf("%d), &a),其中sancf返回成功匹配读取的个数,当输入不匹配时返回0,while跳出循环。(3<a<10)表示((3<a)<10)始终为true,不表示判断a是否在(3,10)区间的数,应该配合逻辑运算符使用(3<a &&a <10)
- 逻辑运算符(! && ||)
- 逻辑非的优先级高于算术运算符,逻辑与和逻辑或的优先级低于关系运算符。
- 逻辑非从右至左运算。eg:int j=6;int i=!!j;。i结果为1。
- 逻辑与和逻辑或的短路运算。逻辑与有假则假,先假短路;逻辑或有真则真,先真短路。
- 位运算符(<< >> ~ | ^ &)
- 赋值运算符(=及其扩展赋值运算符)
- 只有确定值的变量才能成为左值。
- 条件运算符(?:)
- 逗号运算符(,)
- 指针运算符()* &
- 求字节运算符(sizeof)
- 不是函数,得到常量或变量所占用的空间大小。
- 强制类型转换运算符((类型))
- 分量运算符(. ->) —结构体
- 下标运算符([]) —数组
- 其它(如函数调用运算符()) —函数
关系表达式与逻辑表达式
双目运算符:需要左操作数和右操作数。
单目运算符:只需要一个操作数。
算术运算符>关系运算符>逻辑与或
相同优先级的运算符从左至右进行结合
eg:5>3&&8<4-!0。注意逻辑与是否存在短路,因此应该先计算5>3而不是!0,最终1&&0结果为0。
OJ练习
平闰年
能被4整除但不能被100整除,或者能被400整除
#include <stdio.h>
int main()
{
int year;
scanf("%d", &year);
(year%4==0 && year%100!=0||year%400==0)?printf("yes"):printf("no");
return 0;
}
整型,字符型,浮点型三个变量的求和值
考察scanf读取多种类型;混合类型运算,输出格式的控制;混合输入时%c前需要加入一个空格
#include <stdio.h>
int main()
{
int i;
char j;
float k;
scanf("%d %c %f", &i, &j, &k);
printf("%.2f", (float)i+j+k);
return 0;
}
if-else
单句也注意加上花括号,养成良好习惯的同时防止修改后出错。
while循环
“当型”循环,表达式的值非0时,执行while中的内嵌语句。先判断表达式后执行语句,注意跳出循环的条件从而避免死循环。while后面不能加分号,否则也会进入死循环。
for循环
可用于循环次数不确定而只给出循环结束条件的情况,让完全可以代替while。一般形式为for(表达式1;表达式2;表达式3) {语句};
- 先求解表达式1。
- 再求解表达式2,若其值为真(非0),则先执行for语句中对应的内嵌语句。
- 求解表达式3,再转会第2步继续判断执行。
- 循环结束,跳出for循环,并执行后面的程序。
值得注意的是:
- 循环语句中必须有且只能有两个分号,用于分割表达式1、表达式2、表达式3。
- 表达式1、表达式2、表达式3都可省略。
- 3个表达式的作用:
- 表达式1:
- 设置初始条件,只执行一次。
- 可以为零个、一个或多喝变量设置初值。
- 表达式2:
- 循环条件的表达式,用于判断是否继续循环。
- 在每次循环体前先执行此表达式,决定是否继续执行循环。
- 表达式3:
- 循环的调整,用于使得表达式2的结果不断趋近非真从而跳出循环。
- 同样可以设置为任意数量个变量的调整。
- 表达式1:
- 表达式1和表达式3的值都可以在结构体中进行调整。
continue语句
结束本次循环,即跳过循环体中下面尚未执行的语句,在for循环中会接着进行是否执行下次循环的判断,在while中要具体分析。
break语句
结束整个循环过程,在for循环中不再判断执行循环的条件是否成立。
OJ练习
回文数判断
输入一个整型数,判断是否是对称数,如果是,输出yes,否则输出no,不用考虑这个整型数过大,int类型存不下,不用考虑负值;
一般使用while循环,利用中间变量存储首位和末位,设置跳出while循环条件。彻底理解for循环的机制后减少了中间变量的使用,有效利用3个表达式的作用。
#include <stdio.h>
int main() {
int a;
scanf("%d", &a);
for (; a > 10; a = a / 10 % 10) {
if (a / 10 != a % 10) {
printf("no");
break;
}
if (a < 10) printf("yes");
}
return 0;
}
n的阶乘
利用while或者for循环计算n!的值。1≤n≤10。
#include <stdio.h>
int main() {
int total = 1, n;
scanf("%d", &n);
for (1; n > 0; n--) total *= n;
printf("%d", total);
return 0;
}
换零钱
某人想将手中的一张面值100元的人民币换成10元、5元、2元和1元面值的票子。要求换正好40张,且每种票子至少一张。问:有几种换法?
int main() {
int n = 0;
for (int i = 1; i <= 83; i++) {
for (int j = 1; j <= 42; j++) {
for (int k = 1; k <= 15; k++) {
for (int z = 1; z <= 9; z++) {
if ((i + j + k + z == 40) && (i + j * 2 + k * 5 + z * 10 == 100)) n++;
}
}
}
}
printf("%d", n);
return 0;
}
一维数组
一组具有相同数据类型的数据的有序集合。
声明数组要遵循以下规则:
- 数据名的命名规则和变量名相同,即遵循标识符命名规则。
- 定义数组时,需要指定数组中元素的个数,即数组长度。
- 元素个数要使用常量表达式,不能使用变量表达式。C语言不允许对数组的大小做动态定义,即数组的大小不依赖于程序运行过程中变量的值。
- 定义时,可以只对一部分元素使用花括号对应初始化,剩余未赋值的元素默认为0。
- 使一个数组中的全部元素值为0,可写作int a[10]=0;
- 可以不指定数组长度直接赋值,但不推荐使用,元素个数不直观。int a[]={1, 2, 3};
数组的访问越界
编辑器并不检查程序对数组下标的引用是否存在数组的合法范围内,访问越界会导致错误的使用到对应地址的其它常量。
数组的传递
数组传递到子函数不能通过sizeof得到长度,子函数得到的是数组的指针,即数组的起始地址。通过指针是可以修改数组元素值的。
字符数组
以'\0'(值为0)为结束符。
scanf读取字符串操作时,匹配到空白符(空格、tab或回车)会自动往字符数组中添加结束符。
联系scanf的用法,%s自动忽略空白符。
gets函数与puts函数
gets函数类似于scanf函数,用于读取标准输入。scanf函数在读取字符串时遇到空白符会自动结束读取,因此当输入的字符串中存在空格时,需要使用gets函数进行读取。
注意:在字符串中'\0'不会被统计进字符串的长度中,而字符数组会统计'\0'进字符数组的长度中。
gets函数格式:char *gets(char *str);
gets函数一次读取一行,直到匹配到换行符自动结束并在字符串数组中添加\0,换行符遗留在缓冲区。
puts函数类似于printf函数,用于输出标准输出。
puts函数格式:int puts(char *str);
str系列字符串操作函数
str系列字符串操作函数主要包括strlen、strcopy、strcmp、strcat等。
- strlen:统计字符串长度。size_t strlen(chat *str);
int my_strlen(char a[]) { int i=0; while (a[i]) i++; return i; }
- strcpy:将某个字符串复制到字符数组中。char *strcpy(chat *to, const char *from);
- strcmp:比较两个字符串的大小。int strcmp(const chat *str1, const char *str2);
- strcat将两个字符串连接到一起。char *strcat(char *str1, const *str2);
OJ练习
统计数字出现的个数
输入N个数(N小于等于100),输出数字2的出现次数。
#include <stdio.h>
#define N 100
int main() {
int n, count = 0;
int a[N];
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
(a[i] == 2) ? count++ : 0;
}
printf("%d", count);
return 0;
}
比较字符串
读取一个字符串,字符串可能含有空格,将字符串逆转,原来的字符串与逆转后字符串相同,输出0,原字符串小于逆转后字符串输出-1,大于逆转后字符串输出1。例如输入 hello,逆转后的字符串为 olleh,因为hello 小于 olleh,所以输出-1。
注意strcmp标准C中并不是返回-1和1,而是负值和正值。
不必反转,直接比较对应相反位置的字符,利用ASCII码进行字符串的大小比较,同时使用二分法遍历。
#include <stdio.h>
#include <string.h>
#define N 100
int main() {
char info[N];
gets(info);
int len = strlen(info);
int result;
for (int i = 0; info[i] != 0 && i < len / 2; i++) {
result = info[i] - info[len - i - 1];
if (result == 0) continue;
else {
(result < 0) ? (result = -1) : (result = 1);
break;
}
}
printf("%d", result);
return 0;
}
指针
指针的定义
在内存区域中每个字节都对应一个编号,即”地址”。
在程序定义变量进行编译时,系统会给变量分配内存单元。按变量地址存取变量值的方式称为“直接访问”。
而另一种,将存放变量值的地址放到一种特殊的存放地址的变量中,即指针变量,这种通过指针变量存取变量值的方式称为“间接访问”。
定义格式:基本数据类型 *指针变量名;
- 指针变量前面的“*”表示该变量为指针变量。
- 自定义指针变量时必须指定其类型。指针变量的类型必须与存放地址所对应值的类型一致。
- &和*优先级别相同,但要按照自右向左的方向结合。
- int *a, b, c;与int *a, *b, *c;不同。
指针变量的初始化是某个变量取地址来赋值,不能随机写数定义。
注意:指针与指针变量是两个概念。
- 指针:一个变量的地址。
- 指针变量:存放地址的变量。
- 指针变量大小:寻址范围为64位即8字节,寻址范围为32为即4字节(考研吗中往往强调程序时32位的)。
取地址操作符欲取值操作符
取地址操作符为&,也称为引用。用于获取变量的地址值,即指针。
取值操作符*,也称为解引用。用于获取变量中的地址值对应的值。
指针的传递
使用场景一般为函数传参。
C语言的函数调用是值传递,实参赋值给形参。
进程地址空间图:
指针传递即传递地址值(指针)给形参,即可通过形参修改实参值。
指针的偏移
使用场景一般为数组索引。数组名中存储了数组的起始地址。
对指针(地址)进行加减就是指针的偏移。加就是向后偏移,减就是向前偏移,惩处是没有意义的。
偏移长度是偏移量个基类型长度,因此*(p+i)互相等价于a[i]。void*类型的指针不确定地址对应值的基类型,因此是不能进行偏移的。
程序解析:change函数要对c数组进行修改必须获取到c数组的地址。c作为一个数组名存储了数组的起始地址传递给change函数。
指针与动态内存申请
malloc函数需要头文件<stdlib.h>。
void *malloc(size_t size);
- size_t为int型变量表示要开辟的内存块的大小(以字节为单位),告诉函数需要动态开辟多少个字节的空间。
- 返回值为不能便偏移的void*类型的指针,结束后返回给主函数动态开辟好的空间块的首地址,以便后续进行对这块内存空间的使用。
- 向操作空间申请,并分配空间给指针变量时需要强制转换为对应的类型。
- 指针本身的大小,与其指向的空间大小不同。
释放申请的空间时,必须是malloc返回的地址。
栈的效率比堆高得多,但栈空间是有限的。
注意:函数使用的栈空间在使用完后会被释放,当函数返回指针(地址)时,被释放后的空间会被其它内容覆盖出现乱码。
堆空间在整个进程中有效,不因为函数调用结束而被消除,只有free才能释放堆空间。