C语言基础知识
非常基础的知识,适合初学者,这是自己开始学习C语言的一个小记录,参考C语言中文网来学习的。
C语言基础知识
- 1)输出
- 2)输入
- 3)类型转换
- 4)字符串的定义
- 5)[字符串常用处理函数(string.h)](http://www.cplusplus.com/reference/cstring/):link:
- 6)运算符优先级
- 7)选择结构与循环结构
- 8)[内存管理](https://blog.csdn.net/sunweixiang1002/article/details/80407886):link:
- 9)函数声明和函数定义
- 10)作用域
- 11)预处理命令
- 12)枚举enum关键字
- 13)[指针](http://c.biancheng.net/c/80/):link:
- 14)结构体(struct)
- 15)typedef关键字
- 16)const关键字
- 17)static关键字
- 18)extern关键字
- 19)命令行参数
- 20)文件读写
1)输出
格式控制符 | 说明 |
---|---|
%c | 输出一个单一的字符 |
%hd、%d、%ld | 以十进制、有符号的形式输出 short、int、long 类型的整数 |
%hu、%u、%lu | 以十进制、无符号的形式输出 short、int、long 类型的整数 |
%ho、%o、%lo | 以八进制、不带前缀、无符号的形式输出 short、int、long 类型的整数 |
%#ho、%#o、%#lo | 以八进制、带前缀、无符号的形式输出 short、int、long 类型的整数 |
%hx、%x、%lx %hX、%X、%lX | 以十六进制、不带前缀、无符号的形式输出 short、int、long 类型的整数。如果 x 小写,那么输出的十六进制数字也小写;如果 X 大写,那么输出的十六进制数字也大写。 |
%#hx、%#x、%#lx %#hX、%#X、%#lX | 以十六进制、带前缀、无符号的形式输出 short、int、long 类型的整数。如果 x 小写,那么输出的十六进制数字和前缀都小写;如果 X 大写,那么输出的十六进制数字和前缀都大写。 |
%f、%lf | 以十进制的形式输出 float、double 类型的小数,小数点后面为6位数,不足补0 |
%e、%le %E、%lE | 以指数的形式输出 float、double 类型的小数。如果 e 小写,那么输出结果中的 e 也小写;如果 E 大写,那么输出结果中的 E 也大写。 |
%g、%lg %G、%lG | 以十进制和指数中较短的形式输出 float、double 类型的小数,并且小数部分的最后不会添加多余的 0。如果 g 小写,那么当以指数形式输出时 e 也小写;如果 G 大写,那么当以指数形式输出时 E 也大写。 g规定整个数为6位(整数小数一起) |
%s | 输出一个字符串 |
例子1:
int a1=20, a2=345, a3=700, a4=22;
printf("%-9d %-9d %-9d %-9d\n", a1, a2, a3, a4);
输出:20 345 700 22
##%-9d中,d表示以十进制输出,9表示最少占9个字符的宽度,宽度不足以空格补齐,-表示左对齐。综合起来, %-9d表示以十进制输出,左对齐,宽度最小为9个字符。(20 ---->和20一起,占9个字符)
- 小数默认为double类型
- puts():输出字符串并自动换行,该函数只能输出字符串,与gets()输入对应
2)输入
函数 | 缓冲区 | 头文件 | 适用平台 |
---|---|---|---|
scanf()(适用各种数据) | 有缓冲区 | stdio.h | 所有 |
gets()(用来输入字符串的) | 有缓冲区 | stdio.h | 所有 |
getchar()用来输入单个字符的) | 有缓冲区 | stdio.h | 所有 |
getch()(不回显) | 无缓冲区 | conio.h | 只适用Windows |
getche() | 无缓冲区 | conio.h | 只适用Windows |
注:1、gets() 能读取含有空格的字符串,而 scanf() 不能;
scanf() 读取字符串时以空格为分隔,遇到空格就认为当前字符串结束了,所以无法读取含有空格的字符串。
而gets() 认为空格也是字符串的一部分,只有遇到回车键时才认为字符串输入结束,所以,不管输入了多少个空格,只要不按下回车键,对 gets() 来说就是一个完整的字符串。
2、getch/getche无缓冲区,输入一个字符后会立即读取,不用等待用户按下回车键;
3、scanf() 可以一次性读取多份类型相同或者不同的数据,getchar()、getche()、getch() 和 gets() 每次只能读取一份特定类型的数据,不能一次性读取多份数据。
1、scanf
-
在读取字符串的时候,scanf() 遇到空格就认为字符串结束了,不再继续读取了,所以无法读取含有空格的字符串;
-
scanf() 并不是直接让用户从键盘输入数据,而是先检查缓冲区,处理缓冲区中的数据,如果缓存中有数据,而你又不重新输入,那么就会直接从缓存中读取数据;
-
会忽略换行;
-
空白符在大部分情况下都可以忽略,但是当控制字符串不是以格式控制符 %d、%c、%f 等开头时,空白符就不能忽略了,它会参与匹配过程,如果匹配失败,就意味着 scanf() 读取失败了。scanf(“a=%d”, &a);
-
扩展:scanf 加上:{%[*] [width] [size]type | ’ ’ | ‘\t’ | ‘\n’ | };
scanf("%[abcd]", ptr); ##只读取abcd这几个字符,一旦遇到不是这几个字符的,就不读取了 scanf("%[^abcd]", ptr); ##只要读取到abcd任意一个字符,就停止读取 scanf("%10[^abcd]", ptr); ##这样结果字符串最多只能包含10个字符(除'/0'字符外)。 scanf("%*s", ptr); ##只读取,不赋值,ptr的值不会因为这句话而改变
2、getchar
- 如果缓冲区中没有内容,那么等待用户输入;如果有内容,哪怕一个字符(回车、换行也算),也会直接从缓冲区中读取数据,不会等待用户输入。
- 例子:如果不适用getchar函数,那么输入’a=100’后,按回车,就会直接输出,不会等b的输入;使用了之后,按回车,继续输入 b=100;
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a=0, b=0;
scanf("a=%d", &a);
getchar(); //借助该函数将 \n 从缓冲区中清除:
scanf("b=%d", &b);
printf("a=%d, b=%d\n", a, b);
system("pause");
return 0;
}
运行结果:
a=100↙
b=100↙
a=100, b=100
3)类型转换
自动转换规则:
- 左边的可以 自动向右边转换,注意float是向double转换的
(图片来源:C语言中文网)
4)字符串的定义
- char str1[]=“helloC!!”; 读取、写入(一般用这个,既满足输出又满足输入)
- char *str2=“helloC!!”; 读取
printf()、puts() 等字符串输出函数只要求字符串有读取权限,而 scanf()、gets() 等字符串输入函数要求字符串有写入权限,所以,第一种形式的字符串既可以用于输出函数又可以用于输入函数,而第二种形式的字符串只能用于输出函数。
-
字符数组只有在定义时才能将整个字符串一次性地赋值给它,一旦定义完了,就只能一个字符一个字符地赋值了
1、char str[7]; str = "abc123"; //错误 2、str[0] = 'a'; str[1] = 'b'; str[2] = 'c';//正确 3、char str[]="abc123"; //正确
-
字符串结束标志
在C语言中,字符串总是以`'\0'`作为结尾,
所以`'\0'`也被称为字符串结束标志,或者字符串结束符。
char str[]="123asd" //会自动添加'\0' 实际上数组长度为7
char str[] = {'a', 'b', 'c'};
需要注意的是,逐个字符地给数组赋值并不会自动添加'\0',例如:
数组 str 的长度为 3,而不是 4,因为最后没有'\0',
这就需要我们手动去添加了:char str[] = {'a', 'b', 'c','\0'}
char str[7] = "abc123";
"abc123"看起来只包含了 6 个字符,我们却将 str 的长度定义为 7,
就是为了能够容纳最后的'\0'。如果将 str 的长度定义为 6,它就无法容纳'\0'了。
str[i] = 0; //也可以写作 str[i] = '\0';根据 ASCII 码表,字符'\0'的编码值就是 0。
更加专业的做法是将数组的所有元素都初始化为“零”值
char str[30] = {0}; //将所有元素都初始化为 0,或者说 '\0'
``````c
在C语言中,字符串总是以`'\0'`作为结尾,
所以`'\0'`也被称为字符串结束标志,或者字符串结束符。
char str[]="123asd" //会自动添加'\0' 实际上数组长度为7
char str[] = {'a', 'b', 'c'};
需要注意的是,逐个字符地给数组赋值并不会自动添加'\0',例如:
数组 str 的长度为 3,而不是 4,因为最后没有'\0',
这就需要我们手动去添加了:char str[] = {'a', 'b', 'c','\0'}
char str[7] = "abc123";
"abc123"看起来只包含了 6 个字符,我们却将 str 的长度定义为 7,
就是为了能够容纳最后的'\0'。如果将 str 的长度定义为 6,它就无法容纳'\0'了。
str[i] = 0; //也可以写作 str[i] = '\0';根据 ASCII 码表,字符'\0'的编码值就是 0。
更加专业的做法是将数组的所有元素都初始化为“零”值
char str[30] = {0}; //将所有元素都初始化为 0,或者说 '\0'
-
求字符串长度 strlen(字符串名字strname),返回的是一个整数,不包括’\0’
在头文件<string.h>里面
5)字符串常用处理函数(string.h)????
函数名 | 作用 | 备注 |
---|---|---|
strcat(str1,str2) | 把str1和str2拼接起来 | 返回值为 str1 的地址,str1的长度要>=(str1的长度+str2的长度) |
strcpoy(str1,str2) | 把str2的内容复制到str1中 | str1的内容会被覆盖,str1的长度要>=str2的长度 |
strcmp(str1,str2) | 若 str1 和 str2 相同,则返回0;若 str1 大于str2,则返回大于 0 的值;若str1 小于 str2,则返回小于0 的值 | 字符本身没有大小之分,strcmp() 以各个字符对应的 ASCII 码值进行比较。 从两个字符串的第 0 个字符开始比较,如果它们相等,就继续比较下一个字符,直到遇见不同的字符,或者到字符串的末尾。 |
1.strstr(str1,str2) 2. strchr(str1,‘单个字符’),返回该字符在str1中第一次出现的位置,从1开始 | 查看str1中是否有子串str2;若有,返回从该子串后面所有的字符,若无,返回’ '空 | char str[] =“This is a simple string”; char * pch; pch = strstr (str,“simple”); puts (str)// 输出simple string |
6)运算符优先级
优先级 ????
算术运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符;
括号>单目运算符>双目运算符>三目运算符;
注:逻辑运算符中“逻辑非 !”除外(需了解!!)
7)选择结构与循环结构
- switch case:
1、case 后面必须是一个整数,或者是结果为整数的表达式,但不能包含任何变量
2、default 不是必须的。当没有 default 时,如果所有 case 都匹配失败,那么就什么都不执行
-
while:
//统计从键盘输入的一行字符的个数, //本例程序中的循环条件为getchar()!='\n' //其意义是,只要从键盘输入的字符不是回车就继续循环。 //循环体n++;完成对输入字符个数计数。 #include <stdio.h> int main(){ int n=0; printf("Input a string:"); while(getchar()!='\n') n++; printf("Number of characters: %d\n", n); return 0; }
-
break:
一个break语句只向外跳一层
8)内存管理????
(图片来源:C语言中文网)
- 我们可以使用size命令来查看一个目标文件各个段的大小 ????
命令:size a.out
显示:
text data bss dec hex filename
1896 616 8 2520 9d8 a.out
注:dec:十进制总和
hex:十六制总和
int a = 0; /* a在全局已初始化数据区 */
char *p1; /* p1在BSS区(未初始化全局变量) */
int main(void)
{
int b; /* b在栈区 */
char s[] = "abc"; /* s为数组变量, 存储在栈区 */
/* "abc"为字符串常量, 存储在已初始化数据区 */
char *p1, p2; /* p1、p2在栈区 */
char *p3 = "123456"; /* "123456\0"已初始化在数据区, p3在栈区 */、
static int c = 0; /* c为全局(静态)数据, 存在于已初始化数据区 */
/* 另外, 静态数据会自动初始化 */
p1 = (char *)malloc(10); /* 分配的10个字节的区域存在于堆区 */ 或者p1 = (char *)malloc(10*sizeof(int));
p2 = (char *)malloc(20); /* 分配得来的20个字节的区域存在于堆区 */
free(p1);
free(p2);
}
1、BSS段
- 通常用来存放程序中未初始化的全局变量和静态变量(变量刚刚被定义时,系统默认为0或者null,还没有被程序员赋值)
int sum[1000];//默认数组里面每个数为0
2、数据区(data)
- 通常用来存放*被明确初始化的全局变量、静态变量(包括全局静态和局部静态)、常量数据(常量字符串char str=“hello!!”)
//一个不在任何函数内的声明(全局数据):
int maxcount = 99;
//使得变量maxcount根据其初始值被存储到初始化数据区中。
static mincount = 100;
//这声明了一个静态数据,如果是在任何函数体外声明,则表示其为一个全局静态变量;
//如果在函数体内(局部),则表示其为一个局部静态变量;
//另外,如果在函数名前加上static,则表示此函数只能在当前文件中被调用。
3、代码区(text)
-
通常是指用来存放程序执行代码的一块内存区域,代码区通常是只读的,防止代码被修改;
-
在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等;
4、栈区(stack)
- 栈又称堆栈, 是用户存放函数的参数值、程序临时创建的局部变量,也就是说我们函数括弧"{}"中定义的变量(但不包括static 声明的变量,static 意味着在数据段中存放变量);
- 我们可以把堆栈看成一个寄存、交换临时数据的内存区;
- 是一块连续的内存区域;
5、堆区(heap)
-
存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc 等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free 等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减),一般由程序员分配和释放,若程序员不释放,程序结束时有可能由OS回收;
-
堆段由程序员自己管理,即显式地申请和释放空间;
-
静态分配:编译器在处理程序源代码时分配。
动态分配:程序在执行时调用malloc库函数申请分配。
-
函数:
序号 | 函数和描述 |
---|---|
*void calloc(int num, int size) | 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。 |
void free(void*address) | 该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。 |
void*malloc(int num) | 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。 |
**void realloc(void address, int newsize) | 该函数重新分配内存,把内存扩展到 newsize。 |
注:**注意:**void * 类型表示未确定类型的指针。C、C++ 规定 void * 类型可以通过类型转换强制转换为任何其它类型的指针。char *p= (char *)malloc( 200 * sizeof(char) )
9)函数声明和函数定义
- 对于多个文件的程序,通常是将函数 [变量也可以] 定义放到源文件(
.c
文件)中,将函数的声明放到头文件(.h
文件)中,使用函数时引入对应的头文件就可以,编译器会在链接阶段找到函数体。 - 有了函数声明,函数定义就可以出现在任何地方了,甚至是其他文件、静态链接库、动态链接库等。
#include <stdio.h>
//函数声明
int sum(int m, int n); //也可以写作int sum(int, int);
int main(){
int begin = 5, end = 86;
int result = sum(begin, end);
printf("The sum from %d to %d is %d\n", begin, end, result);
return 0;
}
//函数定义
int sum(int m, int n){
int i, sum=0;
for(i=m; i<=n; i++){
sum+=i;
}
return sum;
}
10)作用域
-
局部变量:定义在函数内部的变量,它的作用域仅限于函数内部, 离开该函数后就是无效的,再使用就会报错。
-
全局变量:在所有函数外部定义的变量,它的作用域默认是整个程序,也就是所有的源文件,包括 .c 和 .h 文件。
-
变量的使用遵循就近原则,如果在当前的局部作用域中找到了同名变量,就不会再去更大的全局作用域中查找.
-
只能从小的作用域向大的作用域中去寻找变量,而不能反过来,使用更小的作用域中的变量。
int a, b; //全局变量,对所有函数都有效
void func1(){
//TODO:
}
float x,y; //全局变量,对fun1()无效
int func2(){
//TODO:
}
int main(){
//TODO:
return 0;
}
/*a、b、x、y 都是在函数外部定义的全局变量。C语言代码是从前往后依次执行的,由于 x、y 定义在函数 func1() 之后,所以在 func1() 内无效;而 a、b 定义在源程序的开头,所以在 func1()、func2() 和 main() 内都有效。*/
11)预处理命令
常用预处理
指令 | 说明 |
---|---|
#include | 包含一个源代码文件 |
#define | 定义宏 |
#undef | 取消已定义的宏 |
#if | 如果给定条件为真,则编译下面代码 |
#ifdef | 如果宏已经定义,则编译下面代码 |
#ifndef | 如果宏没有定义,则编译下面代码 |
#elif | 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码 |
#endif | 结束一个#if……#else条件编译块 |
#include
- 概念:叫做文件包含命令,用来引入对应的头文件(
.h
文件) - 注:使用尖括号
< >
和双引号" "
的区别在于头文件的搜索路径不同:
1、使用尖括号`< >`,编译器会到系统路径下查找头文件;
2、而使用双引号`" "`,编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。
##也就是说,使用双引号比使用尖括号多了一个查找路径,它的功能更为强大。
#define 宏定义
-
概念:所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串,是简单的字符串替换。
-
格式:#define 宏名 字符串【
字符串
可以是数字、表达式、if 语句、函数等】 -
这里所说的字符串是一般意义上的字符序列,不要和C语言中的字符串等同,它不需要双引号程序中反复使用的表达式就可以使用宏定义,例如:
#define M (n*n+3*n) #undef M //其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用 #define M(y) y*y+3*y //带参数的宏定义 // TODO: k=M(5); //宏调用 #define N 100 void main(){} int a=20+N;} //a为120 表示之后代码中的N其实代表100
-
#if、#ifdef、#ifndef
- #if 后面跟的是“整型常量表达式”,而 #ifdef 和 #ifndef 后面跟的只能是一个宏名
- #ifdef 可以认为是 #if defined 的缩写,它的意思是,如果当前的宏已被定义过,则对“程序段1”进行编译,否则对“程序段2”进行编译。
12)枚举enum关键字
- 枚举和宏其实非常类似:宏在预处理阶段将名字替换成对应的值,枚举在编译阶段将名字替换成对应的值。我们可以将枚举理解为编译阶段的宏;
- 其实就是优化了#define命令,因为#define一次只能定义一个宏名,如果有很多都需要去定义,那么会导致宏名过多,代码松散,不够简洁。C此时便可以使用枚举(Enum)类型,一次性能够列出所有可能的取值,并给它们取一个名字。
- 作用范围是全局的
- 格式:
enum typeName{ valueName1, valueName2, valueName3, ...... };
如:
enum week{ Mon, Tues, Wed, Thurs, Fri, Sat, Sun };
//枚举值默认从 0 开始,往后逐个加 1(递增);也就是说,week 中的 Mon、Tues ...... Sun 对应的值分别为 0、1 ...... 6。
//我们也可以给每个名字都指定一个值:
enum week{ Mon = 1, Tues = 2, Wed = 3, Thurs = 4, Fri = 5, Sat = 6, Sun = 7 };
//更为简单的方法是只给第一个名字指定值:
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };
//同时定义多个
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };
enum week a = Mon, b = Wed, c = Sat;
13)指针????
- 概念:将内存中字节的编号称为 地址(Address)或 指针(Pointer),即数据在内存中的地址称为指针,如果一个变量存储了一份数据的指针,我们就称它为指针变量
- 定义的时候需要带 ’ * ',之后赋值的时候可以不带
- 深入了解,参考该文????
##变量指针
int a=100,b=200;
int *p; //或者定义并赋值:int *p=&a;
p=&a; //此处的p不用加*,代表指向a,获得a的地址; int *p=&a
p=&b; //指针的值可以改变 *p=100 此处是改变p的值。而不是地址,需要加 *
##连续定义:
int *a, *b, *c; //a、b、c 的类型都是 int*
- a 只需要一次运算就能够取得数据,而 *p 要经过两次运算,多了一层“间接”;使用指针是间接获取数据,使用变量名是直接获取数据ps=stus,前者比后者的代价要高
(图片来源:C语言中文网)
- a 和*p是等价的, &a 和 p是等价的; *p值改变的话,a的值也会变
#include<stdio.h>
int main()
{
int a=12;
int*pa=&a;
printf("a=%d, *pa=%d\n",a,*pa); //a和*p的值都是12
printf("&a=%#X, pa=%#X\n", &a,pa); //&a 和 p的都是0X859ACE3C
int b=100;
int *pb=&b;
printf("b=%d, *pb=%d\n", b, *pb);
printf("&b=%#X, pb=%#X\n", &b, pb) ;
*pa=*pb;//改变*pa的值,也就改变了a的值
printf("pa=%#X, pb=%#X\n", pa, pb);//但是pa的指向并没有变,仍然指向a
printf("a=%d, *pa=%d\n", a, *pa);//a的值变为100
// pa=pb;//改变了pa的指向,使它指向了b
// printf("pa=%#X, pb=%#X\nps=stusps=stus", pa, pb); //pa的指向和pb的一样
// printf("a=%d, *pa=%d\n", a, *pa);//但是a的值没有变,只是pa不再指向a了
return 0;
}
- 使用 指针来遍历数组
#include <stdio.h>
int main()
{
int arr[] = { 99, 15, 100, 888, 252 };
int len = sizeof(arr) / sizeof(int); //求数组长度
int i;
for(i=0; i<len; i++)
{
printf("%d ", *(arr+i) ); //*(arr+i)等价于arr[i]
} //使*p=arr后,也可以用*(p+1)来遍历数组
printf("\n");
return 0;
}
#数组指针,数组指针指向的是数组中的一个具体元素,而不是整个数组
int *p=arr;//arr 本身就是一个指针,可以直接赋值给指针变量 p
##或者
int *p=&arr[0];//arr 是数组第 0 个元素的地址,因此arr、p、&arr[0] 这三种写法都是等价的,它们都指向数组第 0 个元素,或者说指向数组的开头
int *p=&arr[2]; 也可以写成 int *p=arr+2;//因为arr和arr[0]等价!!
- 二维数组指针????: *(*(p+0)+0) 表示二维数组的第一行第一个数(????有点复杂,需仔细理解 )
int (*p)[4] = a;//括号中的*表明 p 是一个指针,它指向一个数组,数组的类型为int [4],这正是 a 所包含的每个一维数组的类型。
//等价关系:
a+i == p+i
a[i] == p[i] == *(a+i) == *(p+i)
a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == *(*(a+i)+j) == *(*(p+i)+j)
int *(p1[5]); //指针数组,是一个数组,只是每个元素保存的都是指针,可以去掉括号直
int *p1[5];
int (*p2)[5]; //二维数组指针,是一个指针,它指向一个二维数组,不能去掉括号
14)结构体(struct)
- 用来存放一组不同类型的数据,是一种数据类型,是一种创建变量的模板,编译器不会为它分配内存空间;
- 格式
一般格式:
struct 结构体名{ps=stus
结构体所包含的变量或数组
};
struct stu
{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在学习小组
float score; //成绩
} stu1, stu2;//创建结构体 名为 stu,同时定义 两个结构体变量
struct stu stu1, stu2;//定义结构体变量
stu1.name="tom";//获取结构体里面的变量
struct stu *pstu = &stu1;//结构体指针,与数组不一样,要想取得结构体变量的地址,必须在前面加&
pstu->name="tom";//结构体指针获取结构体里面的变量
稍微复杂一点,需要仔细理解:
struct stu
{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在小组
float score; //成绩
}stus[] = {//定义了名为stus的结构体数组,并为其赋值
{"Zhou ping", 5, 18, 'C', 145.0},
{"Wang ming", 3, 17, 'B', 144.5}
}, *ps;//定义了一个结构体指针
位域
- 是一类特殊的结构体,当你只想使用数字0或1的时候,就可以使用这个结构体,设置位域为1;
- 格式:
struct
{
type [member_name] : width ;
}; - 描述:
- type 只能为 int(整型),unsigned int(无符号整型),signed int(有符号整型) 三种类型,决定了如何解释位域的值。
- member_name 位域的名称。
- width 位域中位的数量。宽度必须小于或等于指定类型的位宽度。
- 示例:
/* 通常当你定义一个int类型的变量时, 你只要用到8以内的数,那么此时你就可以用位域来进行设置了 8的二进制数是1010,四位数,那么我们就设置位域为3, 就是只可以用0~7这8个数,如果一旦大于7 ,就会报warnning,将变量置为0 */ #include<stdio.h> int main(int argc, char const *argv[]) { struct number { unsigned int age:3;//表示该变量占4个字节,32位,但是只有3位可以被使用 }; struct number a; a.age=4; printf("%d\n",sizeof(a));//4 a.age=3; printf("%d\n",a.age);//3 a.age=8; printf("%d\n",a.age);//0 return 0; }
15)typedef关键字
- 使用关键字 typedef 可以为类型起一个新的别名。typedef 的用法一般为:
typedef oldName newName;
//如给结构体起别名
typedef struct stu
{
char name[20];
int age;
char sex;
} STU;
STU body1,body2;
//为指针类型定义别名:表示 PTR_TO_ARR 是类型int * [4]的别名,它是一个二维数组指针类型
typedef int (*PTR_TO_ARR)[4];
PTR_TO_ARR p1, p2;
*(p1+1)//表示二维数组的第一行
- typedef与#define的区别 :
//1) #define可以使用其他类型说明符对宏类型名进行扩展,但对 typedef 所定义的类型名却不能这样做
#define INTERGE int
unsigned INTERGE n; //没问题
typedef int INTERGE;
unsigned INTERGE n; //错误,不能在 INTERGE 前面添加 unsigned
//2)在连续定义几个变量的时候,typedef 能够保证定义的所有变量均为同一类型,而 #define 则无法保证。例如:
#define PTR_INT int *
PTR_INT p1, p2;
经过宏替换以后,第二行变为:
int *p1, p2;
这使得 p1、p2 成为不同的类型:p1 是指向 int 类型的指针,p2 是 int 类型。
相反,在下面的代码中:
typedef int * PTR_INT
PTR_INT p1, p2;
p1、p2 类型相同,它们都是指向 int 类型的指针。
16)const关键字
- 使得变量的值或者是指针的指向不能被修改,被const修饰以后,变量就相当于一个常量了;
- 语法及使用:
const int MAXAGE=101;//MAXAGE被固定,之后不能再修改它的值
MAXAGE=200//会报错
- const和指针一起使用
//可以修改指向,但是指向的数据不能被修改,
//如p1指向a,a的值不可以修改,但是p1可以改变指向,可以指向b;
const int *p1;
int const *p2;
//只读,p3本身的值不能被修改,指针的指向不能变,但是指向的数值可以被修改
//如果p3指向的是a,那么就不可以再指向别的数据,但是a的值可以修改
int * const p3;
//指向和指向的数据都不能修改
const int * const p4;
int const * const p5;
const:读取
非const:读取、写入
总结:将非 const 类型转换为 const 类型是允许的,降低权限。
17)static关键字
-
使用static修饰局部变量
- 静态局部变量存储于进程的全局数据区,即使函数返回,它的值也会保持不变
-
使用static修饰全局变量
- 普通全局变量对整个工程可见,其他文件可以使用extern外部声明后直接使用。也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量)。
- 静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。
-
使用static修饰函数
- 静态函数只能在声明它的文件中可见,其他文件不能引用该函数
- 不同的文件可以使用相同名字的静态函数,互不影响
18)extern关键字
- 可以使用在后面定义的全局变量和函数
#include<stdio.h>
int main()
{
func(); //1
extern int num;//对后面的全局变量声明
extern int func();//对后面的函数声明
printf("%d",num); //2
return 0;
}
int num = 3;
int func()
{
printf("%d\n",num);
}
- 在一个文件中引用另外一个文件中的全局变量和函数(局部变量不可以)
//main.c
#include<stdio.h>
int main()
{
extern int num;//使用a.c中的全局变量num
printf("%d",num);
return 0;
}
//a.c
#include<stdio.h>
int num = 5;
void func()
{
printf("fun in a.c");
}
注:extern关键字只需要指明类型和变量名就行了,不能再重新赋值;
但是在声明之后就可以使用变量名进行修改了,如下:
extern int num;
num=1;
19)命令行参数
- 执行程序时,可以从命令行传值给 C 程序,从外部控制程序,而不是在代码内对这些值进行硬编码
- 使用 main() 函数参数来处理:main(int argc , char *argv[])/main(int argc , char **argv)
- argc 是指传入参数的个数;
- argv[] 是一个指针数组,指向传递给程序的每个参数/**argv是一个二级指针,指针字符串;
- argv[0] 存储程序的名称(a.out),argv[1] 是一个指向第一个命令行参数的指针,*argv[n] 是最后一个参数。如果没有提供任何参数,argc 将为 1,否则,如果传递了一个参数,argc 将被设置为 2。
- 使用方法:在命令行输入:/a.out 命令行参数1 命令行参数2,…
20)文件读写
访问模式
模式 | 描述 |
---|---|
r | 打开一个已有的文本文件,允许读取文件。如果文件不存在的话,不会自动创建 |
w | 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件(在保证路径存在的情况下)。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。 |
a | 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。 |
r+ | 打开一个文本文件,允许读写文件。 |
w+ | 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。 |
a+ | 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。 |
如果处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式:
"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"
打开文件
- FILE *fopen( const char * filename, const char * mode );
写入文件
- int fputc( int c, FILE *fp ); 只读取一个字符
- char *fgets( char *buf, int n, FILE *fp ); 遇到换行就不读了
- int fscanf(FILE *fp, const char *format, …); 遇到空格或换行符号就不会读了
关闭文件
- int fclose( FILE *fp );
二进制 I/O 函数
下面两个函数用于二进制输入和输出:
-
size_t fread(void *ptr, size_t size_of_elements,
size_t number_of_elements, FILE *a_file); -
size_t fwrite(const void *ptr, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);
这两个函数都是用于存储块的读写 - 通常是数组或结构体。
下一篇: java语言基础知识---数组Array