C语言知识点
基本数据类型、常量和变量
1.C语言的基本数据类型
short 2字节、int 4字节、long 4字节、char 1字节、float 4字节、double 8字节
2.字符常量
- 八进制常量:开头必须是0,且八进制是0-7之间组成的数,例如,029就是错误的八进制表示方式。
- 十六进制常量:0X开头,包含字母ABCDEF,不区分大小写,例如0x与0X一样,0Xaa与0xAA,都是正确的。
- 实数型常量:必须有小数点,例如定义double a=1就是错误的,必须写成double a=1.0,另外写成a=1,0中间是逗号,就更是错误的了。
- e与E,表示以10为底数的幂数,且e与E后面必须跟整数,若是小数,也是错误的,例如3.2e1.5 ,这里1.5是小数,所以错的。
- 字符常量只能用单引号括起来,不能用双引号或其它括号。
- 符常量只能是单个字符,不能是字符串。
- 字符可以是字符集中任意字符。ASCII 字符集中的可显示字符可以参与算术运算,例如’3’+'5’是把’3’和’5’的 ASCII值相加,得到104,即字符’h‘。
- 整型常数的后缀有:u或U(unsigned)、l或L(long)、u/U与l/L的组合(如:ul、lu、Lu等)
3.字符串常量
- 用双撇号" "括起来的字符或字符串
- 字符串常量"a"和字符常量’a’是不同的
- 在每一个字符串常量的结尾,系统都会自动加一个字符’\0’作为该字符串的结束标志
- '\0’是ASCII码为0的字符,不会引起任何控制操作,不可以显示。
- 若" "中能看见的字符有n个,则该字符串在内存中所占的内存空间为n+1字节。
4.scanf()和gets()
scanf()和gets()函数都是输入函数,输出函数另外是printf()和puts()
scanf:
- 显式输入提示使用printf
- 格式说明中只能指定输入数据的总宽度,不能对实型数指定小数位的宽度
- 使用%c格式输入字符时,若格式控制字符串中没有普通字符,则认为所有输入是字符为有效字符。
- 当输入数据的个数小于输入项的个数时,程序等待输入,直到满足要求为止。当输入数据的个数大于数据项的个数时,多余的数据并不消失,而是作为下一个输入操作时的输入数据。
eg:
scanf("%c%c%c",&a,&b,&c);
输入为:d e f
则把“d”赋给a,“ ”赋给b,“f”赋给c
区别:
- scanf()输入数据时,遇到空白字符(空格、制表符、回车等)就会停止;gets()只有碰到回车才会停止。
- scanf()输入字符型数组时,格式为scanf("%s",str)。【注意这里的str没有地址符&,因为格式控制符%s在格式输出和输入函数中,要求字符串的首地址作为输入和输出项,字符数组名就是存放字符数组的首地址】
- scanf()可以同时接受多个字符串【可以输入多个字符,但是中间不能出现空格,一旦出现空格,即认为是另一个新的字符串】,而gets()一次只能接受一个字符串【只要没有键入回车,都认为是一个字符串】。
例1:
#include<stdio.h>
void main()
{
char s1[100], s2[100];
gets(s1);
gets(s2);
printf("s1=%s\n", s1);
printf("s2=%s", s2);
}
- 结果:
例2:
#include<stdio.h>
void main()
{
char s1[100], s2[100];
scanf("%s%s", s1, s2);
printf("s1=%s\n", s1);
printf("s2=%s\n", s2);
}
- 结果
5.getchar()和putchar()
getchar():
- 功能是从系统默认的输入终端输入一个字符,可以是字符字母、数字字符和其他字符等。
- 注意:getchar函数等待输入直到按回车键才结束,回车键前所输入的全部字符都会在屏幕上显式,但只有第一个字符作为函数的返回值。
- 是scanf("%c", c)的替代品,除了更加简洁,没有其它优势。
eg:
#include<stdio.h>
void main()
{
char c;
c=getchar();
putchar(c);
}
结果:
abc
a
putchar():
- 与getchar()相对应,都在stdio.h头文件中,用于打印字符,相当于printf(ch,%c)的简洁版;
- 其中,ch可以是一个字符变量或者常量,也可以是一个转义字符。
6.printf函数
- 格式控制字符串中格式说明与输出项相应当个数一致,对应的类型必须匹配。若格式说明少于输出项的个数,多余的输出项不予输出;;若格式说明多于输出项的个数,对于多余的格式将输出毫无意义的数字。
- 在格式控制字符串中,除了合法的格式说明符之外,可以包含任意的合法字符(包括转义字符),这些字符在输出时将原样输出。
- 若需要输出一个百分号,则应在格式控制字符串中用两个连续的百分号“%%”表示。
7.puts函数
- 用来输出一个字符串,一般形式为
put(str);
- str是存放字符串的起始地址,puts函数从这一地址开始依次输出存储单元中的字符,遇到第一个’\0’符号时结束输出,并自动换行。puts函数一次只能输出一个字符串。
运算符和表达式
1.算术运算符的结合性
- 单目运算符从右向左结合
- 双目运算符从左向右
- 注意:虽然= += -+ 之类为双目运算符,但是它的结合性为从右向左,三目运算符也是。
2.||和&&和!逻辑运算
- 优先级:!> && > ||
- || 有一真则真 【先运算左边的式子,如果为true,那么直接返回结果为true,如果左边的式子不为true,再去运算右边式子】
- && 全真则真 【先运算左边的式子,如果是false,直接返回结果为false,否则再去运算右边的式子,如果是true结果就是true,如果是false,结果就是false】
3.条件运算符
- 一般形式:表达式1?表达式2:表达式3
- 条件运算符的优先级低于关系运算符和算术运算符,但高于赋值运算符
- 结合方向是自右向左 。。 eg:
a>b?a:c>d?c:d
应理解为a>b?a:(c>d?c:d)
- 表达式1的类型可以与表达式2和表达式3的类型不同,并且表达式2和表达式3的类型也可以不同,此时表达式的值的类型为二者较高的类型。
4.逗号运算符
特点:
- 优先级别最低;
- 自左往右执行表达式;
- 返回值为表达式最后一个。
例1:
- 结果
0,0,5
- 赋值运算符优先级高于逗号运算符,将c=(a-=a-5)看成一个整体。先执行c=(a-=a-5) => c=a=5
- 再执行(a=b,b+3),先执行a=b=0,再执行b+3 ,没有保存计算结果,对b的值没有影响
int a=0,b=0,c=0;
c=(a-=a-5),(a=b,b+3);
printf("%d,%d,%d\n",a,b,c);
例2:
- 结果
0,0,3
- 从左到右依次执行,返回值为最后一个表达式。
- 先执行a-=a-5 => a=5,但是并不作为结果返回。
- 继续执行(a=b,b+3),先执行a=b => a=b=0 【注意此时a的值由5变为0】。继续执行b+3,b+3表达式不改变b的值,但是(a=b,b+3)这个表达式返回b+3的值,即3。
- 此时c=(a-=a-5,(a=b,b+3))变为c=(a=5,3)。返回值为最后一个表达式,即c=3
int a=0;b=0;c=0;
c=(a-=a-5,(a=b,b+3));
printf("%d,%d,%d\n",a,b,c);
5.运算符优先级
- 算术运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符。逻辑运算符中“逻辑非 !”除外。
- 按位取反>左移和右移>按位与>按位异或>按位或
- ,运算符优先级最低,-= += = 优先级比+ - * /低
6.强制类型转换
- 自动转换:精度低转精度高;强制转换:精度高转精度低
- 类型标识符和表达式都应该加括号。eg:(int)(x+y)
- 无论强制转换还是自动转换,都是为了本次运算的需要而对变量的数据长度进行临时性转换,而不改变该变量定义时的类型。
7.sizeof和strlen
sizeof:
- 关键字 sizeof 是一个单目运算符,而不是一个函数,用于测定某一种类型数据所占存储空间长度的运算符。与函数 strlen 不同,它的参数可以是数组、指针、类型、对象、函数等
strlen:
- strlen 是一个函数,它用来计算指定字符串 str 的长度,但不包括结束字符(即 null 字符)。
注意:
- 若不对1.2这个数字定义类型,则1.2这个数字是double类型,即占八个字节。
程序控制结构
1.switch
- 关键字switch后面括号内的表达式可以为整型、字符型和枚举型。
- 关键字case和常量表达式之间一定要有空格,如
case 10:
不能写成case10:
- 通常有规律地书写各case,并且default写在最后。
- “case常量表达式”只相当于一个语句标号,表达式的值和某常量表达式相等则转向该标号执行,但不能在执行完该标号的语句块后自动跳出整个switch语句体,所以出现了继续执行后面语句的情况。为了避免,可以使用break语句,跳出switch语句体。
2.do-while和while循环
do-while
- 循环格式:
do{
Statement _1;
Statement _2;
}while(Exp_cntrl);//分号不可丢
- 流程图:
while
- 循环格式:
while(Exp_cntrl)
{
Statement_1;
Statement_2;
}
- 流程图:
3.for语句
- 一般形式:
for(表达式1;表达式2;表达式3) 语句;
- 三个表达式都可以是任意合法的表达式,甚至是逗号表达式,即每个表达式都可由多个表达式组成。
- 三个表达式都是任选项,都可以省略。但是分号间隔符不能省略。
- 循环体可以是一个分号,即空语句。
- 例如,在下列程序中,
sum+=i
就未被反复执行
for(i=1;i<=100;i++) ;
sum+=1;
4.break语句和continue语句
break
- 一般与if语句配合使用,一般形式:
break;
- break语句只能在循环语句和switch语句中使用,但当break出现在循环体中的switch语句时,其作用只是跳出该switch语句,并不能终止循环的执行。
continue
- 只能用在循环体中,一般形式是:
continue;
- 结束本次循环,即不再执行循环体中continue语句之后的语句,转入下一次循环条件的判断与执行。一般也要配合if语句使用。
二者区别:
break语句结束整个循环过程,不再判断执行循环的条件是否成立;而continue语句则是只结束本次循环,而不是终止整个循环的执行。
数组
1.一维数组
引用:
- 下标是数组元素在整个数组中的顺序号,从0开始。
- 下标可以是整型常量、整型变量或者整型表达式,也可以是字符表达式或枚举类型表达式。
初始化:
- 对所有元素赋值
- 对部分元素赋值。从前到后依次赋值,未被赋值到的元素自动赋0值。一个数组中全部元素值为0时,可以写成
int b[5]={0};
【只有所有元素值为0时才能这样赋值】 - 通过赋初值来确定数组的大小。eg:
int a[]={1,2,3,4,5};
2.二维数组
引用:
- 下标可以是常量、变量、表达式等,但类型必须是整型
- 注意:数组定义与数组元素的引用有完全不同的含义。数组定义的方括号中给出的是某一维的长度,而数组元素引用时给出的下标是该元素在数组中的位置标识。前者只能是常量,后者可以是常量、变量或表达式。
初始化:
- 分行给二维数组赋初值。
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
- 将所有数据写在一个花括号内,按数组排列顺序对元素进行赋初值。
int a[2][3]={1,2,3,4,5,6};
编译时按照数组在内存中的顺序依次将各初始值赋给数组元素,若数据不足,系统将给后面的元素自动赋初值0 - 对部分元素赋初值,其余元素自动赋初值0。
int a[2][3]={{1},{3},{5}};
等价于int a[2][3]={{1,0},{3,0},{5,0}};
- 定义二维数组必须指定列值(二维),可以省略行值(一维)
输出二维数组的值
#include<stdio.h>
void main()
{
int a[3][4] = { { 1, 2 }, { 0 }, { 4, 6, 8, 10 } };
int i; //行循环变量
int j; //列循环变量
for (i = 0; i<3; ++i)
{
for (j = 0; j<4; ++j)
{
printf("%-2d\x20", a[i][j]);
//“%-2d”,其中“-”表示左对齐,如果不写“-”则默认表示右对齐;
//“2”表示这个元素输出时占两个空格的空间
//所以连同后面的 \x20 则每个元素输出时都占三个空格的空间。
}
printf("\n");
}
}
3.字符数组
定义:
- 存放字符型数据的数组,其中每个数组元素中存放的都是单个字符。字符数组中的元素都可以作为一个字符型变量来使用
- 定义方法:
char c[6]={‘a’,'b','c','d'};
初始化:
- 提供的初值大于数组长度,则编译出错;若小于,则将这些字符按顺序赋给数组中前面那些元素,其余元素默认为空字符(即‘\0’)
- 同二维数组相似
4.字符串与字符数组
字符串与字符数组关系
- 对于字符串常量,编译系统会自动在末尾添加一个’\0’作为结束符。
字符串常量“hello”有5个字符,长度为5,但在内存中占6个字节,最后一个存储的是'\0'
- 字符数组的每个元素可存放一个字符,但并不要求最后一个字符必须为字符串结束标志’\0’
- 只有在字符数组中的有效字符后加上’\0’这一特定情况下,才可以把这种字符数组“看作”字符串变量。
- 字符数组可以用来存放字符串,可以直接用字符串常量来为字符数组初始化。
char c[20]={"computer & c"};
等价于char c[20]="computer & c";
由于"computer & c"是字符串常量,系统会自动加入字符串结束标记’\0’,所以不必人为加入,此时需注意数组越界。注意:char c[12]={"computer & c"}
是错误的,因为没有为’\0’预留空间。char c[]={"computer & c"}
等价于char c[]={'c','o','m','p','u','t','e','r',' ','&',' ','c','\0'};
- 二维字符数组的每行可存放一个字符串,此时,可以将该数组视为一个字符串数组。
char ca[3][5]={{"a"},{"bb"},{"ccc"}};
等价于char ca[][5]={"a","bb","ccc"};
5.字符串处理函数
使用前包含头文件string.h
(1)字符串连接函数strcat
- 格式
strcat(字符串数组名1,字符串2)
- 功能:把字符串2连接到字符数组1中字符串的后面,并删去字符串1的串标志’\0’,结果字符串的长度是两个字符串长度之和。
(2)字符串复制函数strcpy
- 格式为:
strcpy(字符数组名1,字符串2)
- 功能:把字符串2连同串结束标志’\0’复制到字符数组1中。字符串2可以是字符数组或字符串常量,当字符串2是字符串常量时,相当于把一个字符串赋给一个字符数组。
(3)字符串比较函数strcmp
- 格式为:
strcmp(字符串1,字符串2)
- 功能:比较两个字符串的大小
- 函数返回值为一个整数,即相等,返回值为0;1>2,返回值为一个正整数;1<2,返回值为一个负整数。
- 从左到右按照ascii码大小比较两个字符串对应位置上的字符,若相等则继续比较,直到出现不同的字符或遇到’\0’为止。
(4)测字符串长度函数strlen
6.数组元素的遍历、查找、排序等操作
指针
1.指针的定义和赋值
(1)指针变量三要素:变量名、变量类型和变量的值
- 指针的类型时“基类型 * ”。例如,int *p中,指针p其类型为“ int * ”
- 指针所指向的对象可以是变量,也可以是内存中一个连续区域(动态分配)
(2)指针变量的赋值
int i; int *p=&i;
- 其中,“&”为取地址运算符(单目),用来求变量的地址。&i表示i的地址
- 只有同类型的指针变量才可互相赋值
- 指针变量只能存放地址
(3)指针变量的引用
- 取地址运算符“&”
- 指针运算符“ * ”(单目)。运算对象为指针变量或地址。用来访问指针所指向的变量。
int *p,a; p=&a;
“&”和“*”两个运算符的优先级别相同,按自右向左方向结合。计算&*p时,先执行*p,得a,再执行&运算,得a的地址,即&*p与&a相同,都是指变量a的地址。同理,*&a与*p相同,都是p所指向的变量a。
2.指针与数组
(1)指针与一维数组
- 因为数组名a就是a[0]的地址,所以
p=&a[0];
和p=a;
等价
int a[10],*p=a;
- 此时p+1等价于a+1,值均为&a[1];*(p+1)等价于*(a+1),也就是a[1]
➤总之,引用一个数组元素可以有以下两种方法
- 指针法,有*(a+i)和*(p+i)两种表现形式,都是用间接访问的方法来引用数组元素。
- 下标法,有a[i]和p[i]两种形式。在编译时这两种形式也要处理成*(a+i)和*(p+i),即先按“a+i×(一个元素占用字节数)”计算出第i号元素的地址,然后通过指针运算符“*”来引用该元素
➤指针的算术运算和关系运算
①指针的算术运算(指针移动、指针相减)
- 区分*p++与(*p)++的不同。计算*p++时,*和++优先级相同,自右向左结合,等价于*(p++)。因为++是后缀表达式,所以先用p保存的地址值进行“*”运算,得到p所指向的元素值,再使p自增1,使p指向下一个元素。而(*p)++表示p所指向的元素值加1,指针并未移动。
- 区分*++p与++*p的不同。假设p指向a[0],按照“++”和“*”优先级和结合方向,*++p等价于*(++p),计算时,先将p自增1,使p指向下一个元素,再进行*运算,得到a[1]的值。而++*p等价于++(*p),即先取o指向的元素,再将该元素(a[0])自增1,指针并没有移动。
- 基类型相同的两个指针可以相减,但不能相加。若指针p1和p2都指向同一数组a的元素,且p2在p1的后面,则p2-p1就是p1与p2之间的元素的个数。若两个基类型不同的指针相减,结果无意义。
②指针的关系运算(指针比较)
- 两个指针可以使用关系运算符来比较它们的地址值,比较结果为1(真)或0(假)。但只有对基类型相同且指向同一目标(如一串连续的存储单元)的指针比较时,结果才有意义。
(2)指针与二维数组
➤二维数组的地址
二维数组a及三个一维数组可表示为:
a={a[0],a[1],a[2]}
a[0]={a[0][0],a[0][1],a[0][2],a[0][3]}
a[1]={a[1][0],a[1][1],a[1][2],a[1][3]}
a[2]={a[2][0],a[2][1],a[2][2],a[2][3]}
- 数组名a是元素a[0]的地址,即有a=&a[0],而a[0]表示一行,所以a是第0行的首地址;a+1是a[1]的地址,即a+1=&a[1],也就是第1行的首地址,这里
a+1中的“1”代表1行元素所占的字节数(即4×4=16个字节),而不是1个元素所占的字节数
;对于i,有a+i=&a[i],是第i行的首地址,a+i是针对行的,称为“行地址”,因此,有*(a+i)=a[i]。 - a[0]是a[0][0]的地址,即a[0]=&a[0][0],所以a[0]+1就是元素a[0][1]的地址,即a[0]+1=&a[0][1],这里
a[0]+1中的“1”代表1个元素所占的字节数(即4个字节)
;对于j,有等式a[0]+j=&a[0][j];一般地,有a[i]+j=&a[i][j],即a[i]+j是针对列的,称为“列地址”。
➤指向二维数组的指针变量
- 例如:
int a[3][4]; int (*p)[4];
它表示定义了一个指针变量p,p所指向的对象是包含4个int型元素的一维数组。当执行赋值语句“p=a;”后,p指向a[0],p+1指向a[1](等于a+1),这里的“1”代表一行的字节数,即4×16.
3.指针与字符串
➤指向字符数组的指针变量
- 字符数组可以存放字符串,数组名就是字符串的首地址。
➤指向字符串常量的指针变量
- c语言对字符串变量是按照字符型一维数组进行存储的,有编译系统为每一个字符串常量分配一片连续的存储区域,可以将这些连续存储区域的首地址赋给字符型的指针变量,使之指向字符串常量(指向该字符串常量的第一个字符)。这样的指针变量称为字符串指针。
char *pstring="this is a string pointer.";
printf("%s\n",pstring);
- 程序运行时,系统先输出pstring所指向的字符,然后输出pstring+1所指向的字符,再输出pstring+2所指向的字符…如此直到遇到字符串结束标志’\0’为止。注意:’\0’不在输出字符之列。
➤字符数组和字符型指针变量异同:
- 字符数组存放字符串时,是将每个字符和结束标记’\0’存到各个元素中;而在用字符型指针变量处理字符串时,存放的是地址,绝不是将整个字符串放到字符型指针变量中。
- 字符型数组名代表数组的起始地址,是常量,是不能赋值的;而字符型指针变量是变量,必须赋值才可使用,其值可以改变。
char str[14],*ps;
str="this is a string array."; /*错误,数组名str是地址常量,不能更改*/
ps="this is a string pointer."; /*正确,将字符串常量的首地址赋给ps*/
- 在使用字符串指针时,虽然使用下标法访问字符比较方便,但要注意字符串常量不允许改变,而字符串数组是允许改变的。
char str[]="string array", *ps1="string pointer.", *ps2;
/*str是字符串数组,“string pointer是字符串常量”*/
ps2=str;
ps2[0]='S',ps2[1]='T'; /*改变数组元素str[0]和str[1]*/
printf("%s\n",ps2);
ps1[0]='S',ps1[1]='T'; /*错误,不能改变ps1所指向的字符串常量*/
printf("%s\n",ps1);
4.指针与函数
(1)指针作为函数参数【传址】
➤普通指针变量作为函数的参数
- 当形参为指针时,实参可以是指针变量或存储单元的地址值。
➤一维数组名作为函数的参数
- 数组名作为函数的实参和形参时,传递的是数组的首地址
- 形参是数组名:
int scmp(char s1[],char s2[])
和 形参是指针变量int scmp(char *s1,char *s2)
➤二维数组的指针作为函数参数
- 在二维数组的指针作为函数参数进行传递时,形参和实参既可使用指针变量,也可使用数组名。但应区分行地址还是列地址。
(2)指向函数的指针【函数指针】
➤定义
- 把指向函数的指针变量称为函数指针变量,简称函数指针。
- 一般形式
类型标识符 (* 指针变量名) (类型标识符1,类型标识符2,...);
其中第一个类型标识符表示被指函数返回值的类型,圆括号内的类型标识符用以说明所指函数的参数个数和参数的类型,这些类型标识符应与所指函数参数的类型一一对应。如果所指函数没有形参,这一对圆括号也不可以省略。 - eg:
int (*p)(int , int *)
,表示p时一个指向函数的指针变量,所指向函数的返回值必须是整型,且有两个参数,依次为整型和指向整型的指针。 - 用函数指针变量来调用函数的一般形式:
(* 指针变量名)(实参表)
注意:
- 函数指针变量不能进行算术运算,这与数组指针变量不同的。
- 函数调用中“(*指针变量名)”两边的括号不可少,其中的“*”不应该理解为指针运算符,在此处它只是一种指示符号。
➤函数指针作为函数参数
- 函数名作为实参时,应预先进行函数声明。
(3)返回指针值的函数【指针函数】
➤指针函数的定义
- 一个函数的返回值是一个指针(即地址),并把这种返回指针值的函数称为指针函数。
- 定义指针函数的一般形式:
类型标识符 *函数名(形参表)
{
... /*函数体*/
}
- 其中,函数名之前加了“*”,表明这是一个指针函数,即返回值是一个指针。类型标识符是返回的指针值的基类型。
➤指针函数的调用
函数指针和指针函数在写法和意义上有很大区别。int(*p)()和int*p()表示两种完全不同的含义。
- int(*p)()是一个变量声明,表示p是一个指向函数入口地址的指针变量(函数指针),该函数的返回值是整型数据,“(*p)”两边的括号不能少。
- int*p()则是函数声明,表示p是一个指针函数,返回值是一个指向整形数据的指针,“*p”两边没有括号。
5.指针数组和多级指针
(1)指针数组的定义
- 若一个数组的元素为指针,这个数组为指针数组。
- 指针数组是一组有序的指针的集合,各指针均具有相同的基类型。
- 定义指针数组的一般形式:
int * pa[3];
它表示定义了一个指针数组pa,它有三个数组元素,每个元素都是一个指向整型变量的指针。
指针数组和指向二维数组的行指针变量有区别。二者虽然都可用来引用二维数组,但是其表示方法和意义是不同的。
int (*p1)[3],*p2[3];
- p1是指向整型二维数组的行指针变量,数组的每一行都有三个整型元素;(*p1)的括号不可少。
- p2是一个指针数组,其三个元素均为指针变量。
(2)指针数组与字符串
- 指针数组也用来存储一组字符串,其每个元素被赋予一个字符串的首地址。
- 指向字符串的指针数组的初始化比较简单。
char * name[]={"zhang","wang","li"};
- 完成这个初始化赋值之后,name[0]即指向字符串"zhang",name[1]指向"wang"…
(3)多级指针
- 一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量,习惯上称为多级指针。
int a,*p1,**p2;
p1=&a; p2=&p1;
- 其中,p2前面有两个“*”号,相当于*(*p2),表示p2是一个指针变量,所指向的对象又是一个指向int型数据的指针变量。