复习C语言的坑

复习C语言的坑

Scroll Down

(p)review大法好

数据

其实还挺基础的,但是我怕突然知识盲区所以整理记下来
关于那些原码反码补码二进制八进制十六进制啥的都写在了C语言杂货里

循环

while循环

注意花括号的使用

while(n<3)
    printf("n is %d\n", n);
    n++;

注意分号的位置
误用;会导致无限循环

while(n<3);

for循环

1.可以省略一个或多个表达式(不能省略分号),循环中要包含能结束循环的语句
2.第一个表达式不一定是给变量赋初值,可以是printf,执行循环的其他部分之前,只对第一个表达式求值一次或执行一次(应该不考但是觉得有意思)

int num = 0;
for(printf("input nums:\n"; num != 5;)
{
    scanf("%d",&num);
    printf("That's the one I want");
}
input nums:
1
2
3
That's the one I want

3.循环体中的行为可以改变循环头中的表达式(略略略)

省略的写法

init ans=3;
for(n=3;ans<=25;)
    ans *= n;

ans = 6->18->54

死循环

for(;;)

switch循环

注意具有一定的贯穿性

swtich(num)
{   
    case 1:printf("1");
    case 2:printf("2");
    case 3:printf("3");
}

若num为2,程序将打印23
所以要注意break的使用

悬挂的if-else

else始终与同一对括号内最近的未匹配的if结合

if//1
if//2
else//与2匹配
if//1
{
    if//2
}
else//与1匹配

运算符

运算符的优先级问题

由高到低:
| 运算符 | 结合性 |
| --- | --- |
| () [] -> . | 自左向右 |
| ! ~ ++ -- (type) * & sizeof | 自右至左 |
| * / % | 自左向右 |
| + - | 自左向右 |
| << >> | 自左向右 |
| < <= > >= | 自左向右 |
| == != | 自左向右 |
| & | 自左向右 |
| ^ | 自左向右 |
| 或 | 自左向右 |
| && | 自左向右 |
| 或或 | 自左向右 |
| ?: | 自右至左 |
| assignment | 自右至左 |
| , | 自左向右 |

记忆:
优先级最高者并非真正意义上的运算符:

数组下标、函数调用操作符、结构成员选择操作符

单目运算符的优先级仅次于上述运算符,所以在所有真正意义上的运算符中,单目运算符的优先级最高。

(*p)()
*(p++)

单目运算符:只需要一个数的操作符

逻辑非!、按位取反~、自增自减++--、类型转换(类型)、指针运算和取地址符*&、长度运算符sizeof

双目运算符优先级比单目运算符低

双目运算符:对两个变量进行操作

算术运算符(+ - * / %)
移位运算符(& ^ | << >>)
关系运算符(== 、!= < > <= >=)
逻辑运算符(&& || !)
赋值运算符(= ++ -= ...)
条件运算符(?:) 三目运算符吧

自增自减符号的前置后置

左值:可以出现在赋值操作左边的值,非const左值可读可写

右值:可用于赋值操作的右边但不能用于左边的值,右值只能读不能写
前置操作返回+1后的值,返回的是对象本身,所以这是左值
后置才做返回+1前的值,其返回值可以近似理解为与原操作数值相等的常量,所以是一个右值

int i=0, j;
j = ++i;
printf("i=%d, j=%d", i, j);//i=1, j=1
j = i++;
printf("i=%d, j=%d", i, j);//i=2, j=1

关于逻辑运算符的短路问题

&&
如果第一个表达式为false将不再计算第二个表达式
例如:

if(str != null && !str.equals(" "))
int a=1, b=2, c=-3, d=1;
printf("a=%d b=%d c=%d d=%d", a, b, c, d);
d = ((a+1>0) && (++b>0) && (c-4) > 0;
printf("a=%d b=%d c=%d d=%d", a, b, c, d);

第三个表达式凉了,所以

a=1 b=2 c=-3 d=-1
a=1 b=3 c=-3 d=0

||
当第一个条件满足,将不再计算二个表达式

int a=1, b=2, c=-3, d=1;
printf("a=%d b=%d c=%d d=%d", a, b, c, d);
d = ((a+1>0) || (++b>0) || (c-4) > 0;
printf("a=%d b=%d c=%d d=%d", a, b, c, d);
a=1 b=2 c=-3 d=-1
a=1 b=2 c=-3 d=1

函数调用

f();//调用函数f
f;//计算函数f的地址

值传递问题:可见一个突如其来的指针问题

int change(int x)
{
    x = 5;
    return x;
}

int main()
{
    int x = 10;
    change(x);
    printf("%d", x);//10
    return 0;
}

1.png

void swap(int *x, int *y)
{
    int temp;
    temp = *x;
    *x = *y;
    *y = temp;
}

int main()
{
    int x=5, y=10;
    swap(&x, &y);
    printf("x=%d, y=%d", x, y);//x=10, y=5
    return 0;
}

2.png
注:这里提的引用感觉说不准确还是有一点的,毕竟平时C里不太提这个概念,仅供观赏理解

理解递归

看到一个有意思的例子

实例 - 字符串翻转
#include <stdio.h>
void reverseSentence(); 
int main()
{ 
    printf("输入一个字符串: ");
    reverseSentence(); 
    return 0;
} 
void reverseSentence()
{ 
    char c; scanf("%c", &c);
    if( c != '\n')
    { 
        reverseSentence();
        printf("%c",c); 
    }
}

输入:nuaa
输出:aaun

区分几个"空"

NULL:虽然值是0但是是指针类型,空指针

'\0':转义了0,是字符,作为字符串的结束符

0:就是0

"0":字符串,结尾还有一个'\0'

"\0":字符串,两个空字符,strlen出来的结果长度为0

" ":字符串,长度为1,后面还有一个'\0'

所以注意char类型的字符字符字符字符是一个字节,单引号包起来的是一个字节,双引号包起来的后面必有一个'\0'

数组和指针

左值和右值

左值:表示一个占据内存中某个可识别的位置(地址)的对象
右值:可以放在表达式右边的值(嘻嘻)
看一下接下来的内容再加深理解

含义

int arr[4];
sizeof(arr);//4*4=16
&arr+1;
arr+1;

数组名是指向元素类型的指针
&arr指向整个数组,+1后指向了数组末尾
arr指向数组第一个元素,+1后指向第二个元素
且arr是地址常量,不可以更改

辨析几组小东西

int arr[4];
int *p;
arr[3];
*(arr+3);
*p;//不合法,没有确定指向
arr++;//不合法
p++;//通过编译但没什么卵用
char arr[] = "hello";//对字符数组进行了初始化
char *p = "hello";//p指向了字符串常量
arr[0] = 'H';
//*p = 'H';//不合法,字符串常量内容不可更改
//p[0] = 'H';//不合法

字符串安排一下

定义字符串数组时必然要让编译器知道需要多少空间,而且在指定数组元素个数时至少必字符串长度多1,未被使用的元素都被自动初始化为/0

指针数组和字符串数组

指针数组:内含3个指针,不太规则的形状

const char *p[3] = {
    "aaa",
    "bbbb",
    "cccccc",
};

字符串数组:内含3个数组的数组,每个数组内含6个char类型的值,共占用18字节

char str[3][6] = {
    "aaa",
    "bbbb",
    "cccccc",
};

就像一个矩形

aaa\0\0\0

bbbb\0\0

cccccc

输入输出

一个需要注意的地方:

''中围绕的东西是一个char
""中的东西是一个字符串,以\0结尾

printf和scanf

printf

返回打印字符的个数(若输出错误则返回负值)
几个常用的转换说明:

转换说明输出
%c单个字符
%d有符号十进制整数
%f浮点数,十进制计数法
%o无符号八进制整数
%x无符号十六进制整数,使用十六进制数0f
%i有符号十进制整数
%%打印一个百分号

配合转换说明修饰符使用
|修饰符组合| 含义 |
| --- | --- |
| - | 从字段的左侧开始打印该项 |
| + | 有符号值为正则在值前显示加号, 为负则显示减号 |
| %lf | double |
| %ld | long int |
| %lld | long long int |

scanf

注意
1.要原样输出,空白字符(space、tab、enter)作为相邻两个数据的默认分隔符
2.如果输入列表的变量是基本类型一定要注意取地址符&,将字符串读入字符数组中时不使用取地址符
3.返回值是成功读取的项数

putchar() getchar()

getchar没有参数,返回输入的那个字符,且一次只能接收一次字符
注意:将空格和回车等字符作为有效字符输入

putchar有参数,无返回值,参数就是要输入的字符(字符变量或字符常量)且一次只能输出一个字符

puts()和gets()

puts函数用于显示字符串并在末尾添加换行符(会丢弃)
但是如果输入的字符串过长,会导致缓冲区溢出

fgets()和fputs()

char *fgets(char *str, int n, FILE *stream);
int fputs(const char *s, FILE *stream);

注意看一下返回值

fgets

1.第二个参数指明了读入字符的最大数量,若该参数为n,将读入n-1个字符或是遇到第一个换行符为止

2.遇到第一个换行符会将其储存在字符串中

3.若要从键盘输入读取,第三个参数可为stdin

fputs

第2个参数也可改为stdout

迷惑的来了

gets丢弃了输入中的换行符,puts在输出中添加了换行符
fgets保留输入中的换行符,fputs不在输出中添加换行符

文件踩坑了!!!

注意文件的读写方式!!
以及后缀名
一开始创建了.dat文件,经过排序后再读一个不带后缀名的编码出错了

排序算法

冒泡排序

#include<stdio.h>
#include<string.h>

void bubble_sort1(int arr[], int n)
{
	int i, j, tmp;
	//控制外层循环 
	for(i=0;i<n;i++)
	{
		//每次确定一个最大值 
		for(j=0;j<n-1-i;j++)
		{
			if(arr[j] > arr[j+1])
			{
				tmp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tmp;
			}	
		}	
	}	
} 

void bubble_sort2(int arr[], int n)
{
	int i, j, tmp;
	for(i=0;i<n;i++)
	{
		for(j=i+1;j<n;j++)
		{
			if(arr[i] > arr[j])
			{
				tmp = arr[i];
				arr[i] = arr[j];
				arr[j] = tmp;
			}	
		}	
	} 
}

void bubble_sort3(int arr[], int n)
{
	int i, j, tmp, flag;
	for(i=1;i<n;i++)
	{
		flag = 1;
		for(j=0;j<n-i;j++)
		{
			if(arr[j] > arr[j+1])
			{
				tmp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tmp;
			}
			flag = 0;
		}
		if(flag)
			break;
	}
}

选择排序

#include<stdio.h>
void select_sort(int arr[], int n)
{
	int i, j, tmp, min;
	for(i=0;i<n-1;i++)
	{
		min=i;//起初第一个是最小的
        //寻找最小的下标
		for(j=i+1;j<n;j++)
		{
			if(arr[j] < arr[min])
			{
				min = j;	
			}	
		}
		tmp = arr[min];
		arr[min] = arr[i];
		arr[i] = tmp; 
	}
}