前几天复习的c基础

来源:互联网 发布:英伟达游戏优化怎么关 编辑:程序博客网 时间:2024/04/29 01:20

C学习

1 天:大致复习c的基本语法


1 const关键字

刚才提到,默认情况下,变量的值是可以不断改变的。不过,有时候我们会希望变量的值只在定义的时候初始化一次,以后都不能再改变,这个时候我们就可以使用const关键字来修饰变量。

const int i=2

2.printf支持的格式符

除开%dprintf还支持很多格式符,如下表所示(红色表示常用),这份表格的内容不用去死记,用到时再回来查资料即可


printf也可以输出常量


1 #include <stdio.h>

2 

3 intmain()

4 {

5     

6     printf("输出的常量为%d\n",11);

7     

8     return0;

9 }


 


下面的表格描述了在不同编译器环境下的存储空间占用情况


2> 上述变量ab在内存中的存储情况大致如下表所示:


(注:"存储的内容"那一列的一个小格子就代表一个字节,"地址"那一列是指每个字节的地址)

从图中可以看出,变量b占用了内存地址从ffc1~ffc44个字节,变量a占用了内存地址为ffc51个字节。每个字节都有自己的地址,其实变量也有地址。变量存储单元的第一个字节的地址就是该变量的地址。变量a的地址是ffc5,变量b的地址是ffc1

内存寻址是从大到小的,也就是说做什么事都会先从内存地址较大的字节开始,因此系统会优先分配地址值较大的字节给变量。由于是先定义变量a、后定义变量b,因此你会看到变量a的地址ffc5比变量b的地址ffc1大。

注意看表格中变量b存储的内容,变量b的二进制形式是:0000 0000 0000 0000 0000 0000 0000 1010。由于内存寻址是从大到小的,所以是从内存地址最大的字节开始存储数据,存放顺序是ffc4 -> ffc3 -> ffc2 -> ffc1,所以把前面的0000 0000都放在ffc2~ffc4中,最后面的八位0000 1010放在ffc1中。



printf("变量a的地址是%p", &a);



三、负数的二进制形式

1 intmain()

2 {

3     int b = -10;

4     return0;

5 }

在第3行定义了一个整型变量,它的值是-10-10在内存中怎样存储的呢?其实任何数值在内存中都是以补码的形式存储的。

  • 正数的补码与原码相同。比如9的原码和补码都是1001
  • 负数的补码等于它正数的原码取反后再+1。(取反的意思就是0110




 代码块其实就是用大括号{}括住的一块代码。


1 intmain()

 2 {

 3    {

 4         int a =10;

 5         

 6         printf("a=%d", a);

 7    }

 8     

 9     a = 9;

10     

11     return0;

12 }


  • 注意第3~7行的大括号,这就是一个代码块
  • 当执行到第4行时,系统会分配内存给变量a
  • 当代码块执行完毕,也就是执行完第6行代码后,变量a所占用的内存就会被系统回收
  • 因此,变量a的作用范围是从定义它的那行开始,一直到它所在的大括号{}结束,也就是第4~7行,离开这个范围,变量a就失效了
  • 所以,上面的程序是编译失败的,第9行代码是错误的,变量a在第7行的时候已经失效了,不可能在第9行使用



3.数值越界

1> 例子演示

前面已经看到,每种数据类型都有自己的取值范围。如果给一个变量赋值了一个超出取值范围的数值,那后果会不堪设想。


1 #include <stdio.h>

2 

3 intmain()

4 {

5     int c =1024 * 1024 *1024 * 4;

6     

7     printf("%d\n", c);

8     return0;

9 }


 

我们都知道,int类型能保存的最大值是231-1。在第5行给int类型的变量c赋值一个比231-1大的数值:2321024210

先看看在终端中的输出结果:,可以看出输出的值是0

 

2> 结果分析

我们可以简单分析一下为什么将232赋值给变量c之后输出的是0232二进制形式是:1 0000 0000 0000 0000 0000 0000 0000 0000,一共有33位二进制数。变量c占用4个字节,只能容纳32位二进制数,而且内存寻址是从大到小的,因此,变量c在内存中的存储形式是0000 0000 0000 0000 0000 0000 0000 0000,也就是0,最前面的那个1就不属于变量c的了。




3.字符型变量还可以当做整型变量使用

1个字符型变量占用1个字节,共8位,因此取值范围是-27~27-1。在这个范围内,你完全可以将字符型变量当做整型变量来使用。


1 #include <stdio.h>

 2 

 3int main()

 4 {

 5     char c1 = -10;

 6     

 7     char c2 =120;

 8     

 9     printf("c1=%d  c2=%d \n", c1, c2);

10     return0;

11 }


由于第9行用的是%d,表示以十进制整数格式输出,输出结果:。因此,如果使用的整数不是很大的话,可以使用char代替int,这样的话,更节省内存开销。


些说明符一般就是用来修饰int类型的,所以在使用时可以省略int


1//下面两种写法是等价的

 2  short int s1 = 1;

 3  short s2 =1;

 4  

 5 //下面两种写法是等价的

 6  long int l1 = 2;

 7  long l2 =2;

 8  

 9 // 可以连续使用2long

10  long long ll = 10;

11  

12 //下面两种写法是等价的

13  signed int si1 = 3;

14  signed si2 =3;

15  

16 //下面两种写法是等价的

17  unsignedint us1 = 4;

18  unsigned us2 =4;

19  

20 //也可以同时使用2种修饰符

21  signedshort int ss =5;

22  unsignedlong int ul =5;




3.shortlong

1> shortlong可以提供不同长度的整型数,也就是可以改变整型数的取值范围。在64bit编译器环境下,int占用4个字节(32bit),取值范围是-231~231-1short占用2个字节(16bit),取值范围是-215~215-1long占用8个字节(64bit),取值范围是-263~263-1

2> 总结一下:在64位编译器环境下,short2个字节(16)int4个字节(32)long8个字节(64)。因此,如果使用的整数不是很大的话,可以使用short代替int,这样的话,更节省内存开销。

3> 世界上的编译器林林总总,不同编译器环境下,intshortlong的取值范围和占用的长度又是不一样的。比如在16bit编译器环境下,long只占用4个字节。不过幸运的是,ANSI \ ISO制定了以下规则:

  • shortint至少为16(2字节)
  • long至少为32(4字节)
  • short的长度不能大于intint的长度不能大于long
  • char一定为为8(1字节),毕竟char是我们编程能用的最小数据类型

4> 可以连续使用2long,也就是long long。一般来说,long long的范围是不小于long的,比如在32bit编译器环境下,long long占用8个字节,long占用4个字节。不过在64bit编译器环境下,long longlong是一样的,都占用8个字节。

5> 还有一点要明确的是:short int等价于shortlong int等价于longlong long int等价于long long




4.long的使用注意

1> 常量

longint都能够存储整型常量,为了区分longint,一般会在整型常量后面加个小写字母l,比如100l,表示long类型的常量。如果是long long类型呢,就加2l,比如100ll。如果什么都不加,就是int类型的常量。因此,100int类型的常量,100llong类型的常量,100lllong long类型的常量。


1 intmain()

 2 {

 3     int a =100;

 4     

 5     long b =100l;

 6     

 7     longlong c = 100ll;

 8     

 9     return0;

10 }


变量abc最终存储的值其实都是100,只不过占用的字节不相同,变量a4个字节来存储100,变量bc则用8个字节来存储100

其实,你直接将100赋值给long类型的变量也是没问题的,照样使用。因为100是个int类型的常量,只要有4个字节,就能存储它,而long类型的变量b8个字节,那肯定可以装下100啦。


1 intmain()

2 {

3     long b =100;

4     

5     return0;

6 }


 

2> 输出


1 #include <stdio.h>

2 

3 intmain()

4 {

5     long a =100000000000l;

6     

7     printf("%d\n", a);

8     return0;

9 }


在第5行定义了long类型变量a,在第7行尝试输出a的值。注意了,这里用的是%d,表示以十进制整数格式输出,%d会把a当做int类型来输出,它认为a4个字节的。由于along类型的,占用8个字节,但是输出a的时候,只会取其中4个字节的内容进行输出,所以输出结果是:

又是传说的垃圾数据

 

那怎样才能完整地输出long类型呢?应该用格式符%ld


1 #include <stdio.h>

2 

3 intmain()

4 {

5     long a =100000000000l;

6     

7     printf("%ld\n", a);

8     return0;

9 }


注意第7行,双引号里面的是%ld,表示输出1long类型的整数,这时候的输出结果是:


 

如果是long long类型,应该用%lld


1 #include <stdio.h>

2 

3 intmain()

4 {

5     longlong a = 100000000000ll;

6     

7     printf("%lld\n", a);

8     return0;

9 }



5.signedunsigned

1> 首先要明确的:signed int等价于signedunsigned int等价于unsigned

2> signedunsigned的区别就是它们的最高位是否要当做符号位,并不会像shortlong那样改变数据的长度,即所占的字节数。

  • signed:表示有符号,也就是说最高位要当做符号位,所以包括正数、负数和0。其实int的最高位本来就是符号位,已经包括了正负数和0了,因此signedint是一样的,signed等价于signed int,也等价于intsigned的取值范围是-231~ 231 - 1
  • unsigned:表示无符号,也就是说最高位并不当做符号位,所以不包括负数。在64bit编译器环境下面,int占用4个字节(32bit),因此unsigned的取值范围是:0000 0000 0000 0000 0000 0000 0000 0000 ~ 1111 1111 1111 1111 1111 1111 1111 1111,也就是0 ~ 232 - 1



7.不同数据类型所占用的存储空间





第二天

2.在标准C语言中,函数的定义顺序是有讲究的,默认情况下,只有后面定义的函数才可以调用前面定义过的函数


1 int sum(int a,int b) {

2      return a +b;

3 }

4  

5  intmain()

6 {

7      int c = sum(1,4);

8      return0;

9  }


5行定义的main函数调用了第1行的sum函数,这是合法的。如果调换下sum函数和main函数的顺序,在标准的C编译器环境下是不合法的(不过在Xcode中只是警告,Xcode中用的是GCC编译器)




3.如果想把其他函数的定义写在main函数后面,而且main函数能正常调用这些函数,那就必须在main函数前面作一下函数的声明


1//只是做个函数声明,并不用实现

 2  int sum(int a,int b);

 3  

 4 int main()

 5 {

 6      int c = sum(1,4);

 7      return0;

 8 }

 9  

10  //函数的定义(实现)

11  int sum(int a,int b) {

12      return a +b;

13  }


4.在大型的C程序中,为了分模块进行开发,一般会将函数的声明和定义(即实现)分别放在2个文件中,函数声明放在.h头文件中,函数定义放在.c源文件中

下面我们将sum函数的声明和定义分别放在sum.hsum.c


说到这里,有人可能有疑惑:可不可以在main.c中包含sum.c文件,不要sum.h文件了?


大家都知道#include的功能是拷贝内容,因此上面的代码等效于:


这么一看,语法上是绝对没有问题的,但是绝对运行不起来,在链接时会出错。原因:编译器会编译所有的.c源文件,这里包括main.csum.c,编译成功后生成sum.objmain.obj文件,当链接这两个文件时链接器会发现sum.objmain.obj里面都有sum函数的定义,于是报"标识符重复"的错误。



printf("%5d\n", n);

printf("%-5du\n", m);

printf("%-8.2f%10.3f",a,a);

    



大家都知道,数组名代表着整个数组的地址,如果一维数组的名字作为函数实参,传递的是整个数组,即形参数组和实参数组完全等同,是存放在同一存储空间的同一个数组。这样形参数组修改时,实参数组也同时被修改了。形参数组的元素个数可以省略

//btest函数的形参(形式参数)

void test(int b[]) {// 也可以写int b[3]

    b[0] =9;

}


int main()

{

    int a[3];

    a[0] =10;

    

    printf("函数调用前的a[0]%d\n", a[0]);

    

    test(a); // atest函数的实参(实际参数)

    printf("函数调用后的a[0]%d", a[0]);

    return 0;

}



些人可能想不明白,为什么可以省略行数,但不可以省略列数。也有人可能会问,可不可以只指定行数,但是省略列数?

其实这个问题很简单,如果我们这样写:

int a[2][] = {1,2, 3,4, 5, 6}; // 错误写法

大家都知道,二维数组会先存放第1行的元素,由于不确定列数,也就是不确定第1行要存放多少个元素,所以这里会产生很多种情况,可能12是属于第1行的,也可能1234是第一行的,甚至123456全部都是属于第1行的

第三天

字符串

Gets();puts();一次一个字符串puts函数输出一个字符串后会自动换行gets可以读入包含空格、tab的字符串,直到遇到回车为止;scanf不能用来读取空格、tab

2.字符串数组的初始化

char names[2][10] = { {'J','a','y','\0'}, {'J','i','m','\0'} };


char names2[2][10] = { {"Jay"}, {"Jim"} };


char names3[2][10] = {"Jay", "Jim" };





8 char c[3] = {‘’e’,’f’,’’\0};


当我们使用类似第8行的初始化方式时,系统会自动在字符串尾部加上一个\0结束符

凡是有双引号引得都默认加上\0了,不需要自己加



1.字符输出函数putchar

putchar(65); //A

putchar('A'); // A


int a = 65;

putchar(a); // A

上面的3种用法,输出的都是大写字母A

* putchar一次只能输出一个字符,而printf可以同时输出多个字符

printf("%c %c %c",'A', 'B', 'a');

 

2.字符输入函数getchar

char c;

c = getchar();

getchar会将用户输入的字符赋值给变量c

* getchar函数可以读入空格、TAB,直到遇到回车为止。scanf则不能读入空格和TAB

* getchar一次只能读入一个字符。scanf则可以同时接收多个字符。

* getchar还能读入回车换行符,这时候你要敲2次回车键。第1次敲的回车换行符被getchar读入,第2次敲的回车键代表输入结束。