C语言基础

来源:互联网 发布:马云淘宝最新消息 编辑:程序博客网 时间:2024/06/11 12:02

1.#include<stdio.h>的作用是将stdio.h文件复制到所占的位置上,引用自己写的文件用#include "abc.h",系统自带的文件引用需要<>,自己写的用""

2.#include<stdio.h>又叫预编译指令,即在编译之间执行的指令

 .h称为头文件,用来声明一些常用的函数,假如想使用这些函数,就必须包含这个头文件。

这个头文件中只包含函数的原型,并没有实现函数的代码,实现函数的代码在函数库中。Xcode中可以用Command+单击这个头文件可以看到这个头文件中包含的原型

4.C程序运行的基本流程:编译-->(生成.obj文件)-->链接(生成.exe文件)-->运行

编译是将源文件生成01代码,但是.obj文件是不能运行的,因为源文件中运行了许多系统提供的函数(包括main函数)不链接函数库的话,这些函数系统根本找不到怎么能运行呢?所以编译之后还有个链接过程,将一个工程中生成的所有.obj文件链接起来然后和函数库进行链接,最后生成了在Mac系统当中可以运行的可执行文件

5.如果自定义的函数在main函数的后面的话,那么必须在main函数前面添加声明,不然编译器根本不知道有这个函数,因为编译是从上到下按顺序编译的。声明为函数的原型。

6.自己定义头文件abc.h,然后在main()函数中就可以用了。.abc.h里面只是函数原型,所以要定义abc.h包含的函数原型的实现代码,一般以相同的名字命名如abc.c,以.c结尾代表abc这个文件是头文件的实现代码。而abc.c中的函数实现代码链接的时候会添加到main()函数的后面。所以当引用了abc.h的自定义头文件后,就可以引用这个头文件声明的函数,跟在main函数中的自定义函数一样,先声明,代码实现放在后面,跟函数库的使用是一样的。开发大型程序的时候可以一个人写一个文件,而不是许多人同时开发一个文件。

7.如果写头文件的时候,不小心写了两个相同的,或者引用的另一个文件中也包含和主函数一样的头文件,不会出现不可以预知的情况,因为编译平台考虑到了这种情况,所以拥有检查机制,如果有相同的话只会当做一个,所以允许出现函数的重复声明。

#ifndef _ASD_H

    #endif

#define _ASD_H

在头文件中用这个可以防止头文件被重复编译,降低编译器的效率。

因为编译第一次的时候那个头文件的宏已经被定义,第二次包含这个头文件的时候就不会再编译这个头文件。

8.duplicate v. 复制;复印;重复

9.symbol 表示符

10.引用头文件的时候不能这样引用:#include "abc.c"

因为 编译:1main.c-->main.obj  2abc.c-->abc.obj编译阶段一个文件为一个编译单元。编译器不考虑两个文件之间的逻辑关系,只检查每个文件的语法是否错误,所以编译阶段编译器是不会报错的。

 链接:main.obj+abc.obj+C语言函数库,因为main.c中包含了abc.c文件,所以链接的时候会出现错误,因为编译器默认情况下函数不允许重复定义,所以在链接的时候会出现错误。

11.printf("%d",&a);打印出来a的地址。 printf("%x",&a);十六进制打印出来a的地址

12. size_t size = sizeof(int); size_t是个类型,将int占有的字节个数赋值给size

 int也可以使具体的整数或者字符。  

13.用字符数组存储字符串,char arr[]={'a','b','/0'}代表字符串"ab"

也可以 char[3]={'a','b','/0'},也可以char[3]="ab",编译器自动给后面添加/0,字符数组的长度实际上比输出的字符的位数多一位,因为/0也是字符数组中的值。

14.打印输出字符串,printf("%s",arr);当以%s输出的时候,arr输出字符串。

%d输出的时候,输出的是arr字符串的地址。或者printf("%s","abc");

15.printfputs的区别

1.printf可以一次输出多少个字符串,而puts一次只能输出一个字符串。

 2.puts会自动换行,但printf不能自动换行,需要手动换行。

puts(arr);//输出字符串,参数是地址常量。 或者puts("abc");

16./0的作用

  1.无论是用printf还是puts函数用来输出字符串,它们都是以/0作为结束标记,不断查找/0,直到找到为止,如果找不到/0查找范围就可能超出。

17.一维字符数组存放一个字符串,那么存储多个字符串,比如一个班所有学生的姓名,则需要二维数组,char name [15][20]可以存放15个学生的姓名(每个学生的姓名不超过20个字符)

如果要存储两个班的学生姓名,那么可以用三位字符数组char name [2][15][20] 

18.字符处理函数

使用字符串处理函数的时候一定要包含<string.h>头文件

   putchar 一次只能输出一个字符

   getchar字符输入函数,一次只能输入一个字符,同时它有个返回值来接收这个字符。getchar还能读入回车换行符,这时候要敲两次回车,第一次的回车换行符被getchar读入,第二次敲的回车代表输入结束

strlen(字符串地址);测量字符串的长度,不包含/0的字符串的长度。也就是字符的长度。

  从给定的地址开始,计算字符的个数,直到找到/0为止。

strcpy函数strcpy(char *,const char*);将右边的常量字符串传递到左边的字符数组当中,这时候要注意右边的常量字符串长度不能大于左边的字符数组长度-1

strcat(char*,const char *);

将右边的常量字符串追加到左边的字符串中。右边字符串常量会占据左边字符串的/0,拼接完成后添加/0,进行拼接的时候必须保证被追加的字符串数组的长度要大于或者等于拼接后的字符数组的长度。

stcmp 用来比较两个字符串的大小。

 int a = stcmp("字符串1""字符串2");

  函数将字符串1减去字符串2,如果大于0说明字符串1大,如果小于0说明字符串2大。

 函数从字符串的第一个字符开始比较,一直找到/0或者不想同的字符进行比较,第一个不相同才比较第二个,如果第一个字符不同,那么就不再向后比较

strlwr 将字符串中的大写字母转换成小写字母

strupr 将字符串中的小写字母转换成大写字母

25.指针

 1.直接引用

    通过变量名引用变量,由系统自动完成变量名和其存储地址之间的转换,称为变量的"直接引用"方式。

 2.间接引用

 C语言中还有一种间接引用的方式,以变量a为例:首先将变量a的地址放到另一个变量b当中,然后通过变量b的名字,找到变量b的地址,取出b中存储的a的地址,然后读写变量a的值。这就是间接引用。

例如: char *a;

       char b =1;

       a= &b;//b的地址赋给a

       *a =2;//通过a中存储的地址来打开b的存储空间。

26.在同一种编译器环境下,一个指针变量所占用的内存空间是固定的,因为地址的长度是固定的,不会因为数据类型不同导致地址的长度的改变。

27.指针跟数组

int tmp[3]={1,1,2} ; int *b = tmp;

  int change(int a[])

  {

    a[0]=1;

      return 0;

   }

调用: change(tmp); change(b);一样。即使形式参数为int *a,在函数内部一样可以用a[0],无论是不是数组。

28.

int main()

{

 char s[7] = "itcast";

 for(int i=0;s[i]!='\0';i++)

  {

     printf("%c\n",s[i]);

  }

 return 0;

}

for循环的判断条件不一定是i<n;只要是包含i的能够判断出真假的表达式都可以。

也可以用指针来遍历字符串

int main()

{

  char *p="itcast";

 

  for(;*p!='\0';p++)

  {

     printf("%c\n",*p);

  }

}

 

int main()

{

   char s[7]="itcast";

   char *p =s;

  for(;*p!='\0';p++)

  {

     printf("%c\n",*p);

  }

}

下面这个是错误的:

与前一个的区别是:前一个*p改变的是字符串变量,而下面这个是改的字符串常量,所以下面的是错的。

int main()

{

  char *p="itcast";

 

  char *p = 'f';

  return 0;

}

 

29.

int *test(char *p)

{

  return "inadsf"; 

}

30.定义函数指针

 将函数原型中的函数名用 *p代替。

 int (*p)(int ,int );

定义了一个指向函数的指针变量p

被指向的函数:返回值为int类型,接收两个int类型的参数。

 想要调用函数 则令 p = sum;//sum为函数名。

调用函数(*p)(5,6); *p指向函数的入口,也可以直接p(5,6);

 

31.函数指针的作用

//count函数式用来做ab的运算

//至于什么运算取决于p

int count(int a,int b,int (*p)(int,int))

{

 

}

我们可以将函数作为参数传递,当我们有时代码不能变的时候,而又想改变函数的算法,那么将函数作为参数就可以了。

 

32.预处理指令

1.c语言在对源程序编译之前,会先对一些特殊的预处理指令做出解释,(比如之前使用的#include文件包含指令),产生一个新的源程序,(这个过程称为编译预处理),之后再进行通常的编译。

2.为了区分预处理指令和一般的C语句,所有预处理指令都以符号"#"开头,并且结尾不加分毫

3.预处理指令可以出现在程序的任何位置,他的作用范围从它出现的位置到文件尾。习惯上我们尽可能的将预处理指令写在源程序的开头,这种情况下,它的作用范围就是整个源程序文件。

4.C语言提供的预处理指令主要有:

宏定义、文件包含、条件编译

33.#define NUM 6 //不带参数的宏定义

在代码编译之前将所有的NUM全部替换成6.

好处,比如

int a[4]={1234};

for(int i=0;i<4;i++)

{}

如果有多个for的话,如果数组的长度变为5的话,就需要将所有的for中的4和数组中的4都改成5,一旦一个忘了改就会出现错误。

所以数组的元素个数我们一般用宏定义来定义。

宏定义一般用来定义一些常量,它只会代替没有被双引号引着的常量。

例如 char *s ="NUMasd";

//双引号中的NUM并不会被替换为6.

#define sum(a,b) (a+b) //带参数的宏定义

int a = sum(10,10);

//sum(a,b)替换为10+10;

如果用带参数的宏的话,最好将后面的常量带上括号,因为宏定义只会替换而不会做计算。

 

如果是 #define mul (a,b) a*b

    mul(1+1;2+3);

那么计算的结果是1+1*2+3 =6;而不是5

如果是 #define mul(a,b) (a)*(b)

   mul(10,10)/mul(2,2)

计算的结果是:100/2*2=100;而不是25;

所以要用有参数的宏定义最好

#define mul(a,b) (a)*(b)

 

因为宏定义是在编译之前就运行的,而在代码中运行要耗费时间,宏定义效率要高。

但是宏定义是不会检测类型的。

34.条件编译 预处理指令

有时候我们希望程序的一部分代码只有在满足一定条件下才进行编译,否则不参与编译。

#if 条件 1

....

#elif 条件2

.....

#else

....

#endif

预处理指令 条件中不能有变量,因为变量是在程序运行的时候才进行的,而预处理指令是在编译之前进行的。所以只能用宏定义来书写条件。

例如

  #define num 10

int main()

{

  #if num>0

  printf("num大于0");

  #elif num ==0

  printf("num等于0");

  #else

  printf("num小于0");

  #endif

return 0;

}

条件编译只能用宏定义常量来判断

如果条件一成立,那么编译器就会把

#if#elif之间的代码编译进去,注意:是编译进去,不是执行的,跟品是用的if-else是不一样的。

注意,条件编译结束后,要在最后面加上一个#endif,不然后果很严重,如果if条件成立的话,那么整个程序后面的代码全部都不进行编译了。

它是在代码编译之前进行的。

 

35.

#if defined(MAX)

..CODE..

#endif

或者可以这样写:

#ifdef MAX

...code...

#endif

//判断 如果前面已经定义了这个宏,那么执行code代码

#if !defined(MAX)

....

#endif 或者

 

#ifndef MAX

...code...

#endif

 //同上,不过是相反的条件

 

36 预处理指令--文件包含

1.#include <文件名>

系统直接到C语言函数头文件所在的目录中寻找文件

2.#include "文件名"

系统先在源程序当前目录下寻找,如果找不到,再到操作系统的path路径中查找,最后才到C语言函数头文件中查找。

所以<>""代表了系统的两种查找方式。

使用注意:

#include指令允许嵌套包含,比如a.h包含b.h,b.h包含c.h,但是不允许递归包含,比如a.h包含b.hb.h包含a.h

 

 

 

38.C语言有丰富的数据类型和运算符,因此计算能力非常强大,计算过程中使用的值一般用变量来存储。变量也是有分类型的,不同类型的变量有不同的存储类型、不同的生命周期、不同的作用域,C语言也提供了一些关键字来设置变量的属性(比如设置存储类型、生命周期)

1.变量的作用域

C语言根据变量的作用域不同,将变量分为局部变量和全局变量。

 1.局部变量:定义在函数内部的变量。形式参数也是于局部变量。

 2.作用域:局部变量只在定义它的函数内部有效,即局部变量只有在定义它的函数内部使用,其他函数是不能使用它的。

2.全局变量

 1.定义在所有函数外部的变量称为全局变量。

 2.作用域:全局变量的作用范围是从定义变量的位置开始到源程序结束,即全局变量可以被其定义位置之后的其他函数所共享。

 

二、变量的存储类型指:变量存储的地方。

1普通内存、2、运行时堆栈、3硬件寄存器。变量的存储类型决定了变量什么时候创建、什么时候销毁以及它的值能保存多久,也就是决定了变量的生命周期。

C语言根据变量的存储类型的不同,可以把变量分为:自动变量、静态变量、寄存器变量。

 

1.自动变量

  1.定义:自动变量是存储在堆栈中的。

  2.默认情况下所有局部变量都是自动变量。被关键字auto修饰的局部变量都是自动变量,但极少用到这个关键字。

  3.生命周期:在程序执行到声明自动变量的代码块(函数)时,自动变量才被创建;当自动变量所在的代码块执行完毕后,这些自动变量会自行销毁,如果一个函数被重复调用,这些自动变量每次都会被重新创建。

2.静态变量

  1.定义:静态变量是存储在静态内存中的,也就是不属于堆栈。

  2.那些是静态变量:

    1所有的全局变量默认都是静态变量

    2被关键字static修饰的局部变量也是静态变量,但是这个静态变量只是改变了它的生命周期,并没有改变它的作用范围,也就是说它还是局部变量,在自身递归调用的时候不用一直重复创建。

    它是在程序运行到那里的时候才创建这个被static修饰的局部变量。

    3.生命周期:静态变量在程序运行之前创建,在程序的整个运行期间始终存在,直到程序结束。

   

40.查看局部变量和静态局部变量的区别

void test()

{

   //b是一个局部变量,自动变量

   int b = 0;

    b++;

   //c是另一个局部变量,静态变量

   static int c = 0;

   c++

   printf("b=%d,c=%d\n",b,c);

}

int main()

{

  for(int i= 0;i<3;i++)

  {

      test();

   }

return 0;

}

打印的结果为:

b=1,c=1

b=1,c=2

b=1,c=3

因为变量c的声明周期是从它创建开始到整个程序的结束,所以,c不会随着函数的结束被销毁。在第二次经过static int c;的时候自动将它忽略掉了,因为已经创建过了。

注意:虽然static改变了c的生命周期,但是并没有改变它的作用范围,它还是只能在test函数内部使用。

41.寄存器变量

1.定义:存储在硬件寄存器中的变量,称为寄存器变量。寄存器变量比存储在内存中的变量访问效率更高(默认情况下,自动变量和静态变量都是放在内存中的)

2.哪些变量是寄存器变量:

  1.被关键字register修饰的自动变量都是寄存器变量。

  2.只有自动变量才可以是寄存器变量,全局变量和静态局部变量都不行

  3.寄存器变量只限于intchar、和指针类型的变量使用。

3.生命周期:因为寄存器变量本身就是自动变量,所以函数中的寄存器变量在 调用该函数时占用寄存器存放它值,当函数结束时释放寄存器变量的内存,变量消失。

4.注意:

  1.由于计算机中寄存器数目有限,不能使用太多的寄存器变量。如果寄存器变量饱和时,程序将寄存器变量自动转化为自动变量处理。

  2.为了提高运算速度,一般会将一些频繁使用的自动变量定义为寄存器变量,这样程序尽可能的为他分配寄存器存放,而不用内存。

42.static extern不仅可以用在变量上,还可以用在函数上。

 1.如果一个程序中有多个源文件,编译成功会生成对应的多个目标文件,这些文件还不能单独运行,因为这些目标文件之间可能会有关联,比如a.obj可能会调用c.obj中定义的一个函数,将这些关联的文件链接在一起后才能生成可执行文件。

43.

外部函数:如果在当前文件中定义的函数允许访问其他文件访问、调用,就称为外部函数。C语言规定了不允许有同名的外部函数,如果有,会在链接的时候报错。

C默认函数是外部函数。

内部函数:如果在当前文件中定义的函数不允许其他文件访问、调用,只能在内部使用,就称为内部函数。C语言规定不同的源文件可以有相同名字的内部函数,并且互不干扰。

44.exitern外部,完整的定义一个外部函数要用extern关键字,但是他是默认的,所以不常用

45.

#include <stdio.h>

 

extern void one();

//完整的声明一个函数,需要用到extern关键字,表示引用一个外部函数。

extern可以省略。

 

46.内部函数需要用static修饰。

#include "one.h"

void main()

{

   one();

}

one.c文件

#include <stdio.h>

static void one()

{

   printf("调用了one函数");

}

因为main函数中声明了one.c,告诉main函数有这个one函数,所以编译的时候不会报错,但是在链接的时候,因为one函数中没有one函数的定义,所以它会在其他文件中找这个one函数,在one.c中找到了one函数,但是由于这个one函数是个内部函数,不让外部文件调用,所以链接的时候出错,报错:没有找到one这个外部函数。

 

47.如果要在不同的文件中调用相同函数名的内部文件。那么声明和定义都必须加上static,因为默认是extern,所以如果

void one();

static void one()

{}

这样是会报错的,因为没有声明这个内部函数,声明这个内部函数应该:

static void one();

 

48.在默认情况下,一个函数不可以访问在它后面定义的全局变量。

如果必须要这样做那么在main函数前面用extern int a;来声明a,告诉main函数这个变量是存在的,它只是声明,

extern不可以定义变量,但是它可以定义函数。

extern 只能声明全局变量。

extern int a;extern仍然可以省略。。。。所以尽量写上extern.

 

49.C语言中允许多个文件中有相同的名字的全局变量,但是要注意:他们是同一个变量。

 

50.如果一个文件需要用到另一个文件中的全局变量a,那么用extern int a;这个extern也可以省略。。。

 

注意:要想extern int a;至少有一个文件中已经定义了这个int a;

 

51.有时候我们不想我们定义的变量

被其他文件所调用,防止被乱改。这时候我们可以用static修饰

static int a;

//static修饰的全局变量,可以成为内部变量,只能内部访问,别的文件访问不了

 

52.两个文件中有同名的局部变量,那么他们是同一个变量,但是如果有一个或者多个用static修饰,那么代表它们是两个不同的变量,那么这些变量互相不干扰。

 

53.结构体注意点:

1.不允许对结构体本身递归定义:例:

  struct Student

   {

      int age;

      struct Student stu;

    }

2.结构体中可以包含别的结构体

  struct Date

   {

       int year;

       int month;

       int day;

   }

   struct Student

   {

      int age;

      struct Date Birthday;

   }

赋值:

 struct Student a ={18,{2009,10,8}};

调用:

  a.Birthday.year = 2011;

 

3.结构体和数组一样,在定义的时候可以用{}初始化,但是在定义之后用{}来初始化。

54.指向结构体的指针

struct Student a = {2011,12,5};

struct Student *p =&a;

 

调用: p->age; (*p).age;

 

55.定义枚举类型和枚举变量

enum Season{spring,summer,autumn,winter};

enum Season s = spring;

 

枚举元素的值编译器按照整形来处理。

56.typedef

我们可以用typedef给类型定义一个别名。

用法: typedef int Integer;

#define Integer int 用法相似,但还是有些区别,宏定义只是换了个字符串,而typedef 定义了一个类型,因为#define有可能将其他含有这个字符串的变量名改变了。

还可以在别名的基础上再起一个别名。

typedef struct Student{int age;} Student;

这样用Student的时候就不用加上struct

也可以用typedef将指向结构体的指针起别名、

typedef struct Student * PP;

以后PP就代表了结构体类型的指针。

 

指向函数的指针的typedef有些不同

typedef int(*p)(int,int);

不用写在后面,这个p就是函数的指针。

 

typedef char * String

String s2 ="asdf";

可以快速调用字符串。

 

宏定义和typedef的区别

typedef char * String1;

#define String2 char *;

String s1,s2;可以

String2 s1, s2;不可以

因为:宏定义只会替换

会变成:char *s1; char s4;

0 0