1、自动转换发生在不同数据类型运算时,在编译的时候自动完成。
2、转换后不会改变原数据的类型及变量值,只在本次运算中临时性转换。
3、强制转换后的运算结果不遵循四舍五入原则。
4、而10.0%3则是错误的;运算后的符号取决于被模数的符号,如(-1)%3 = -1;而10%(-3) = 1。
5、注意:复合运算符中运算符和等号之间是不存在空格的。
6、使用do-while结构语句时,while括号后必须有分号。
do{
//执行代码块
}while(表达式); //注意这里有分号
7、在不知道循环次数的情况下适合使用while或者do-while循环:
如果有可能一次都不循环应考虑使用while循环
如果至少循环一次应考虑使用do-while循环。
8、在没有循环结构的情况下,break不能用在单独的if-else语句中;
在多层循环中,一个break语句只跳出当前循环。
9、break是跳出当前整个循环,continue是结束本次循环开始下一次循环。
10、
switch(表达式){
case 常量表达式1:执行代码块1 break;
...
case 常量表达式n:执行代码块n break;
default:执行代码块n+1;
}
switch后面的表达式语句只能是整型或者字符类型。
各case和default子句的先后顺序可以变动,而不会影响程序执行结果。
default子句可以省略不用。
11、形参与实参
形参只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。
因此,形参只有在函数内部有效。
注意:函数调用结束返回主调函数后则不能再使用该形参变量。
实参可以是常量、变量、表达式、函数等。
无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值等办法使实参获得确定值。
12、C语言根据变量的生存周期来划分,可以分为静态存储方式和动态存储方式。
静态存储方式:是指在程序运行期间分配固定的存储空间的方式。静态存储区中存放了在整个程序执行过程中都存在的变量,如全局变量。
动态存储方式:是指在程序运行期间根据需要进行动态的分配存储空间的方式。动态存储区中存放的变量是根据程序运行的需要而建立和释放的,通常包括:函数形式参数;自动变量;函数调用时的现场保护和返回地址等。
13、C语言中存储类别又分为四类:
自动(auto)(可以省略),动态存储方式、
**静态(static)**用static修饰的为静态变量,如果定义在函数内部的,称之为静态局部变量;如果定义在函数外部,称之为静态外部变量、
寄存器的(register)
外部的(extern)。
静态局部变量属于静态存储类别,保存在静态存储区的静态存储单元,在程序整个运行期间都不释放;
静态局部变量在编译时赋初值,且只赋初值一次;
如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符(对字符变量)。
为了提高效率,C语言允许将局部变量得值放在CPU中的寄存器中,这种变量叫“寄存器变量”,用关键字register作声明。
void fn(){
register int i; //定义i为寄存器类型变量
}
注意:只有局部自动变量和形参可以作为寄存器变量;
一个计算机系统中的寄存器数目有限,不能定义任意多个寄存器变量;
局部静态变量不能定义为寄存器变量。
用extern声明的的变量是外部变量,外部变量的意义是某函数可以调用在该函数之后定义的变量。
#include <stdio.h>
int main()
{
extern int x; //使用extern可以使用函数之后的变量
printf("extern x=%d\n",x);
return 0;
}
int x=100;
输出为extern x=100
如果没有extern int x,则会报错
14、内部函数(静态函数)与外部函数(默认)
static [数据类型] 函数名([参数])
这里的static是对函数的作用范围的一个限定,static限定该函数只能在其所处的源文件中使用,因此在不同文件中出现相同的函数名称的内部函数是没有问题的
在C语言中能被其他源文件调用的函数称谓外部函数 ,外部函数由extern关键字来定义,形式为:
extern [数据类型] 函数名([参数]) //可默认不写extern
外部函数练习
hello.c
#include <stdio.h>
#include "test.c" //引用test.c文件,可以使用test.c文件的静态函数
extern void printLine();
{
printf("**************\n");
}
int main()
{
say(); //这里调用test.c中的say()
return 0;
}
test.c
#include <stdio.h>
void printLine(); //先进行函数的声明,调用了hello.c文件的printLine方法
static void say() //设置say()为静态函数,外部文件函数不能直接使用,可以导包使用
{
printLine(); //然后使用方法
printf("I love imooc\n");
printf("good good study!\n");
printf("day day up!\n");
printLine();
}
输出结果:
**************
I love imooc
good good study!
day day up!
**************
15、在C中,没有字符串类型,使用字符数组表示字符串
16、在实际工作中,C程序通常运行在linux/unix操作系统下
17、float单精度 6位小数,double双精度 15位小数
18、数组不是基本数据类型,而是构造类型
19、C99标准提供了_Bool型(此时为整数类型,只有0、1赋值,提供<stdbool.h>定义bool代表_Bool,true代表1,false代表0
20、墙砖符号只针对最近的操作数有效,往往使用小括号提升优先级
21、默认传递值的类型:基本数据类型、结构体、共用体、枚举类型
默认传递地址的类型:指针、数组
22、定义常量的两种方式,#define预处理器 const关键字
#define 常量名 常量值
const 数据类型 常量名 = 常量值;
const 在编译和运行时起作用,define在编译的预处理阶段起作用
23、俩种类型的头文件:程序员编写的头文件、C标准库自带的头文件
头文件包含:常量、宏、系统全局变量、函数原型等等
头文件在代码层面有防止重复引入的机制。
头文件只能包含变量和函数的声明,不能包含定义
24、变量的作用域
全局变量:保存在全局区,作用域默认为整个程序,即.c和.h文件
局部变量:保存在栈中,函数被调用时才动态的为变量分配存储单元,作用域为函数内部
同一个作用域变量名不能重复,不同的作用域可以
由{}包围的代码块也拥有独立的作用域
25、
静态局部变量存储于进程的静态存储区(全局性质),只会被初始一次,即使函数返回,值也会保持不变
静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量
适用于定义不需要与其他文件共享的全局变量,能够有效的降低程序模块之间的耦合,避免不同文件同名变量的冲突,不会造成误用。
26、静态函数只能在声明它的文件中可见,其他文件不能引用该函数,不同文件可以使用相同名字的静态函数
27、预处理命令
以#开头的命令
#if _WIN32
#include <windows.h>
#elif __linux__
#include <unistd.h>
#endif
int main() {
//不同的平台下调用不同的函数
#if _WIN32 //识别windows平台
Sleep(5000);
#elif __linux__ //识别linux平台
sleep(5);
#endif
puts("hello~");
getchar();
return 0; }
28、C语言宏定义 #define N 100
29、枚举:一种构造数据类型
#include <stdio.h>
void main() {
enum DAY
{
MON = 1, TUE = 2, WED = 3, THU = 4, FRI = 5, SAT = 6, SUN = 7
};
enum DAY day;
day = WED;
printf("%d", day); //3
getchar();
return 0;
}
enum {
MON = 1, TUE = 2, WED = 3, THU = 4, FRI = 5, SAT = 6, SUN = 7
}day; //该枚举类型只能使用一次
30、头文件的工作原理
31、函数调用规则
1)当调用(执行)一个函数时,就会开辟一个独立的空间(栈)
2)每个栈空间是相互独立的
3)当函数执行完毕后,或者执行到return,会返回到函数调用位置,继续执行
4)如果函数有返回值,将函数值赋给接受的变量,返回值类型要明确,要求返回值和返回类型要匹配,或者可以相互转换(自动或者强制)。
32、&获取地址,如&n,获取变量n的地址;
int* p,声明int型的指针p,
取值符,如p,获取p指针指向的变量的值
33、C语言不支持函数重载,支持可变参数函数
34、局部变量必须初始化
#include <stdio.h>
void main() {
int i; //这里未初始化,直接报错
printf("%d", i);
}
全局变量,系统会自动进行初始化
int i = 0;
char c = '\0';
float f = 0.0;
double d = 0.0;
35、static关键字
静态局部变量(由static修饰的局部变量),如果未赋值,编译器也会把它初始化,且只初始化一次;
普通变量每次执行都会初始化
void fn_static(){
static int n = 10;
printf("static n=%d\n",n); //10
n++; //11
printf("n++=%d",n); //11
}
fn_static(); //10 11
fn_static(); //11 12
36、在一个文件中,使用另外一个文件的全局变量,使用extern引入即可,
静态全局变量,只能在本文件中使用,不能在其他文件中使用
file1.c
int num1 = 10;//普通全局变量
static int num2 = 20;//静态全局变量,只能在本文件中使用,不能在其他文件中使用
file2.c
#include <stdio.h>
//在一个文件中,使用另外一个文件的全局变量,使用extern引入即可
extern int num1;
//extern int num2;
int num2 = 30;
void main() {
printf("num1=%d num2=%d", num1, num2);
}
37、非静态函数可以在另一个文件中通过extern引用,不同的文件可以使用同名的静态函数
38、带参宏定义和函数的区别
宏展开仅仅是字符串的替换,不会对表达式进行计算;宏在编译之前就被处理掉了(预处理阶段),它没有机会参与编译,也不会占用内存。
函数是一段可以重复使用的代码,会被编译,会被分配内存,每次函数调用,就是执行内存中的代码。
#include <stdlib.h>
#include <stdio.h>
#define D(x) (x)*(x)
int SQ(int y) {
return y * y;
}
int main() {
int i = 1;
while (i <= 5) {
printf("%d^2=%d\n", i-1, SQ(i++)); //printf对参数是从右往左计算,从左往右输出
}
int j = 1;
printf("------------\n");
while (j <= 5) {
printf("%d^2=%d\n", j-2, D(j++));
//这里的D表示的宏的具体为(j++)*(j++)
}
system("pause");
return 0;
}
39、
1)预处理功能是C语言特有的功能
2)宏是预处理的一种,用#define来定义,宏调用时以实参代替形参,而不是值传递。
3)文件包含是预处理的一个重要功能,它可以把多个源文件连接成一个源文件进行编译,结果将生成一个目标文件。
4)条件编译允许只编译程序中满足条件的程序段,使生成的目标程序较短,从而减少内存的开销并提高程序的效率。
5)使用预处理功能便于程序的修改、阅读、移植和测试,也便于实现模块化程序设计。
40、数组是构造类型,以地址传递、传递指针的方式,因此当把一个数组传递给一个函数或者变量时,函数/变量操作数组会影响到原数组
double arr[6];
arrLen=sizeof(arr)/ sizeof(double);
数组名就代表该数组的首地址,即arr[0]地址
41、C语言没有专门的字符串变量,没有string类型,通常用一个字符串数组来存放一个字符串。
42、字符串使用null字符(‘0’)终止的一维字符数组,一个以null结尾的字符串,包含了组成字符串的字符。
结论:如果在给某个字符数组赋值时,(1)赋给的元素的个数小于该数组的长度,则会自动在后面加’\0’表示字符串结束,(2)赋给的元素的个数等于该数组的长度,则不会自动添加’\0’
43、对字符数组只能对各个元素赋值,不能用以下方法对字符数组赋值
char str[10];
str="hello world!"//错误
str[0]='y'//ok
对字符指针变量,采用下面方法赋值可以
char* a="yes";
a="hello world!";//ok
即如果定义了一个字符数组,那么它有确定的内存地址(即字符数组名是一个常量);而定义一个字符指针变量时,它并未指向某个确定的字符数据,并且可以多次赋值
常用字符串函数
44、
1)程序中往往依靠检测’\0’的位置来判断字符串是否结束,而不是根据数组的长度来决定字符串长度。因此,字符串长度不会统计’\0’,字符数组长度会统计。
char str1[]=“dssda”;//默认后面加’\0’
char str2[]={‘C’,‘H’,‘I’,‘N’,‘A’};//字符数组后面不会加’\0’,可能会有乱码
2)保证数组长度始终大于字符串实际长度
3)系统对字符串常量也自动加一个’\0’作为结束符。例如’‘ggg"共有3个字符,但在内存中占4个字节,最后一个字节’\0’是系统自动加上的(可以通过sizeof()函数验证)。
4)系统会默认将字符数组的剩余元素空间,全部设置为’\0’,比如char str[6]=“ab”,str内存布局就是[a][b][\0][\0][\0][\0]
45、指针:内存的地址;指针变量:保存了内存地址的变量;获取变量的地址用&;
指针类型:int* ptr = &num;ptr就是指向int类型的指针变量,即ptr是int* 类型;
获取指针类型所指向的值,使用:(取值符号),比如:int * ptr,使用ptr获取ptr指向的值。
即,&获取变量的地址,*ptr获取指针类型变量所保存的地址的数值
46、指针变量声明
int a=1;
int *b=&a;
如果要输出一个变量的地址,使用格式是%p
printf(“b的地址值为%p\n,b存放的值是一个地址为%p\n,b指向的值为%d”,&b,b,*b);
//0x00AFFEC0
//0x00AFFECC
//1
47、指针递增
#include <stdio.h>
const int Max = 3;
void main() {
int var[] = { 1,2,34 };
int i, * ptr;
ptr = var;
for (i = 0; i < Max; i++) {
printf("var[%d]的值为%d\n", i, *ptr);
ptr++;//ptr = ptr + 1;一个int字节数,即加4bit
}
}
48、指针数组:让数组的元素,指向int或其他数据类型的地址(指针),可以使用指针数组
比如有10个字符串,分别保存在10个数组内,这时候就可以定义一个指针数组,每个数组元素为一个字符串数组指针,分别指向这10个字符串,操作起来就方便多了。
49、
1)一个指向指针的指针变量声明,必须在变量名前放置两个星号 int** ptr; //声明了一个指向int类型指针的指针
访问这个值为**p;
2)当函数的形参类型为指针类型时,,使用该函数需要传递指针、地址、或者数组给该形参;
3)数组本身就代表该数组首地址,因此传数组本身就是传地址
50、指针函数:函数的返回值是一个指针(地址)
#include <stdio.h>
char* strLong(char* str1, char* str2) {//未知字符串的长度,可以用指针数组的形式,要返回最长的字符串,可以选择指针函数
//需要从函数中返回多个值,此时使用数组或指针能够很好地完成这样的任务
printf("第一个字符串的长度为%d,第二个字符串的长度为%d\n", strlen(str1), strlen(str2));
return strlen(str1) >= strlen(str2) ? str1 : str2;
}
void main() {
char str1[30], str2[30], * str;
printf("请输入第一个字符串");
gets(str1);
printf("请输入第二个字符串");
gets(str2);
str = strLong(str1, str2);
printf("更长的一个字符串是%s,长度为%d", str,strlen(str));
}
51、
1)指针作为函数返回值,在函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形参,函数返回的指针不能指向这些数据;
2)函数运行结束后会销毁该函数所有的局部数据,这里的销毁并不是将局部数据所占用的内存全部清零,而是程序放弃对它的使用权限,后面的代码可以使用这块内存;
3)C语言不支持在调用函数时返回局部变量的地址,如果需要,则定义局部变量为static变量
char * func (char a) {
retrun &a; //非法
}
#include <stdio.h>
int* Pool(int array[], int size)
{
int* x;
int i = 0;
int a[2] = { 0,1 };
for (i = 0; i < size; i++)
{
a[0] += array[i]; //存储数组元素值的和
a[1] *= array[i]; //存储数组元素值的积
}
//将数组的基地址赋值给整型指针
x = &a[0];
//返回整个数组
return x;
}
int main()
{
int a[] = { 1,2,3,4 };
int* c; c = Pool(a, 4);
Pool(a, 3);//增加这句就会让值改变,栈将数据重新覆盖
printf("Sum = %d\nProduct = %d\n", c[0], c[1]);
getchar();
return 0;
}
Sum = 6
Product = 6
由于该数组是局部变量,因此在函数返回时其数组空间已经作废了,即指针应用一块无意义的地址空间,所以不会有返回值。
如果得到正常的值,只能是幸运的
退出函数的时候,系统只是修改了栈顶的指针,并没有清内存;
所以,是有可能正常访问到局部变量的内存的。
但因为栈是系统自动管理的,所以该内存可能会被分配给其他函数,这样,该内存的内容就会被覆盖;不再是原来的值了。
为了继续保存原来的数组,使main能继续操作数据,则可以使局部变量数组变为静态变量
static int a[2] = { 0,1 };
Sum = 16
Product = 144
编写一个函数,它会生成10个随机数,并使用表示指针的数组名(即第一个数组元素的地址)来返回它们。
#include <stdio.h>
#include <stdlib.h>
int *func(){
static int arr[10];
int i=0;
for(i=0;i<10;i++){
arr[i]=rand();
}
return arr;
}
void main(){
int *p=func();
int i;
for(i=0;i<10;i++){
printf("%d\n",*(p+i));
}
}
52、函数指针:指向函数的指针
例:用函数指针来实现对函数的调用,返回两个整数中的最大值
#include <stdio.h>
int max(int a, int b){
return a>b?a:b;
}
void main(){
int x, y, max Val;
//说明函数指针
//1.函数指针的名字pmax
//2. int表示该函数指针指向的函数是返回int类型
//3.(int, int)表示该函数指针指向的函数形参是接收两个int
//4.在定义函数指针时,也可以写上形参名 int (*pmax)(int x, int y)= max;
int (*pmax)(int , int) = max;//
printf("Input two numbers:");
scanf("%d %d",&x, &y);
//(*pmax)(x, y)通过函数指针去调用函数max
maxVal = (*pmax)(x, y);
printf("Max value: 0%d pmax=%p pmax本身的地址=%p\n" , maxVal, pmax, &pmax);
getchar();
getchar();
}
53、回调函数:函数指针变量作为一个函数的参数来使用,回调函数就是通过函数指针调用的函数。
使用回调函数的方式,给一个整型数组int array[10]赋10个随机数
#include <stdio.h>
int initArray(int* array, int size, int(*func)(void)) {
int i;
for (i = 0; i < size; i++) {
array[i] = func();
}
}
int getRandomNumber(void) {
return rand();
}
void main() {
int i, array[10];
initArray(array, 10, getRandomNumber);
for (i = 0; i < 10; i++) {
printf("%d\n", array[i]);
}
getchar();
}
54、动态创建数组,输入5个学生的成绩,另外一个函数检测成绩低于60分的,输出不合格的成绩
#include <stdio.h>
#include <stdlib.h>
void func(int *p) {
int i;
for (i = 0; i < 5; i++) {
if (p[i] < 60) {
printf("成绩不合格的有:%d", p[i]);
}
}
}
void main() {
int *p, i;
p = (int*)malloc(5 * sizeof(int));
for (i = 0; i < 5; i++) {
scanf("%d", p + i);
}
func(p);
}
55、指针使用一览
56、什么时候用指针
1) 需要改变实参的时候, 只能用指针.
2)传递大型结构并且"只读"其元素的时候,
因为大型结构通过值传递, 需要拷贝其每个元素, 这样效率太低.
3) 需要遍历数组或频繁引用其元素时, 这样效率比使用下标高.
4)动态分配空间时, 必须使用指针.
5)传递数组时, 必须使用指针.
6) 函数返回指针时, 比如fopen[object Object]
56、结构体
struct Student {
char* name;
int age;
char group;
};
结构体成员的类型:基本类型、数组或指针、结构体
在创建一个结构体变量后,需要给成员赋值,否则会导致程序异常终止
#include <stdio.h>
void main() {
struct Student {
char* name;
int age;
char group;
};
struct Student s1; //没有初始化结构体变量的成员
printf("名字:%s", s1.name);
}
定义方式
//方式一
struct Cat{
...
};
struct Cat cat1,cat2;
//方式二
struct Cat{
...
}cat1,cat2;
//方式三
struct{
...
}cat1,cat2;
赋值方式
struct Cat{
char* name;
int age;
}cat1={'a',10},cat2={'b',20};
struct Cat cat3={'c',30};
struct Cat cat4;
//cat4={'d',40};//不能这样赋值
cat4.name='d';
cat4.age=40;
57、结构体举例1
#include <stdio.h>
struct Dog {
char* name;
int age;
double weight;
};
char* func(struct Dog dog) {
static char info[50];
snprintf(info, "名字:%s 年龄:%d 体重:%.2f", dog.name, dog.age, dog.weight);
dog.name = 'c';
return info;
}
void main() {
struct Dog dog;
dog.name = 'a';
dog.age = 10;
dog.weight = 12.3;
char* info = NULL;
info = func(dog);
printf("小狗的信息:%s", info);
printf("小狗的名字:%s", dog.name);
getchar();
}
结构体举例2
#include <stdio.h>
#include <string.h>
struct Visitor {
char name[10];
int age;
double pay;
};
void ticket(struct Visitor* visitor) {
if ((*visitor).age > 18) {
(*visitor).pay = 20;
}
else {
(*visitor).pay = 0;
}
}
void main(){
struct Visitor visitor;
while (1) {
printf("请输入游客的名字:");
scanf("%s", visitor.name);
if (!strcmp("n", visitor.name)) {
break;
}
printf("请输入游客的年龄:");
scanf("%d", &visitor.age);
ticket(&visitor);
printf("游客的票价应该为%.2f\n", visitor.pay);
}
}
58、共用体Union
属于构造类型,可以包含多个类型不同的成员,也被称为联合或者联合体
union data{
...
};
共用体与结构体的区别:结构体的各个成员会占用不同的内存,互相之间没有影响,共用体的所有成员占用一段内存,修改一个成员会影响其余所有成员。
59、文件操作
1)getchar()与putchar()函数
程序会读取一个单一的字符
#include <stdio.h>
void main() {
int c;
printf("输入值:");
c = getchar();
printf("结束");
putchar(c);
}
2)gets()与puts()函数
程序会读取一整行直到改行结束
#include <stdio.h>
void main() {
char str[10]; //超出长度报异常
printf("输入值:");
gets(str);
printf("结束");
puts(str);
}