C语言学习笔记---谭浩强

前段时间有机会去面试了一次,真是备受“打击”(其实是启发),总的来说就是让我意识到了学习工具和学习技术的区别。所以最近在看一些数据结构和算法,操作系统,python中的并行编程与异步编程等东西。然而数据结构那些本来是在看python下的,但是python实在包装了太多的东西而大多的经典教材都是基于C语言的(自然是这样,C语言能够操作直接访问物理地址,能够进行位(bit)操作的特点就决定了。)所以就决定重新学一遍C语言,先重温下谭浩强老师的书籍记录下要点:

文档PDF版:http://pan.baidu.com/s/1eQ4pycq

一:语言:

机器语言:计算机直接识别和接受的二进制机器指令

汇编语言:是直接面向处理器的程序设计语言,操作的对象不是具体的数据,而是寄存器或者存储器,故比其他语言快很多。[通过编译器将其转化为机器语言再执行]

◆由于机器语言和汇编语言是完全依赖于机器特性的,不同机器上不能通用。

高级语言:不依赖具体机器的语言。C语言是一个很小的内核语言,很多的东西都是编译系统提供的“库函数”提供的,如输入输出函数就是standard input output

即是头行的#include<stdio.h>。在连接到库函数时会把此文件的代码放到此处,从而后面的对于如scanf与printf函数的调用就没问题了。

二:运行C语言程序的步骤:

编译                           链接

.c文件----------à.obj (二进制的目标程序) +库函数与其他目标函数------à.exe可执行文件

三:数据类型:

!!! 用sizeof(类型)可返回占的字节数。!!!

整型:short [int]:2字节

int:2(-32768~32768)或4个字节

long [int]:4字节

long long [int]:8个字节

*上四个前面可加unsigned表示取正

char:字符型(可以用单引号,字符串必须用双引号),1字节

以字符的ASCII码存放,而ASCII码为整型

可以把0~127的整数赋给它。

char ch=65 or ‘A’;        // ASCII码十进制下65为’A’,两个写法等效

printf("%d\n",ch);          // 65

printf("%c\n",ch);         // A

printf("%c\n",ch+32);  // a  (’a’的ASCII码为97)

bool:需要#include<stdbool.h>。true or false

bool a,b;

a=score>=60;

b=score<=69;

if (a==true && b==true) printf(“the grade is C\n”)

浮点型:用来表示小数的

单精度浮点型float:4个字节

双精度浮点型double:8个字节,扩大了存储范围

*复数浮点型

枚举型:

空类型:

派生类:指针(*):

数组([ ]):

结构(struct):

公用体类型(union):

★强制类型转换:(类型名)表达式

【只是返回一个多续类型的数据,原数据的类型并没有变化】

b = (double)a;

c = (float)(5%3);

四:变量

代表一个有名字的,具有特定属性的一个存储单元。变量名实际上是以一个名字代表的一个存储地址。【 需要提前申明 】

申明语法:数据类型 变量名[【 =… 】【,变量名n[=…] 】

常变量:

1)  定义符号常量:

#define Pi 3.1415926

则在程序中可以使用Pi来代表3.1415926

2) 定义常变量:(推荐使用)

const float pi=3.1415926

全局变量:

在main()函数外定义的变量

习惯的把全局变量首字母大写

五:运算符:

除法:/      取余:%

int a=5,b=3; a/b ------à1

float c=5,d=3; a/b ----à1.666667

int e=5; float f=3.0; e/f-----à1.666667    //非同化类型会转化为同一类型(更高级类型)后再进行运算

六:关系表达式:返回逻辑的真or假

=               !=               ==              >=              <=

七:逻辑符:

非: !     且: &&     或: II

●算术运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符

(!a)&& (x>y)  等效于不要括号的情况

◆在不用bool时,C语言用0表示假,非0表示真, !0会得到1

1) a=4, 则 !a=0

2) 4 && 0 || 2 的值为1

3) 5 > 3 && 8 < 4 - !0  左边得1。 右边<低于-,故先算4-!0,!级别最高--à4-1=3,8<3得0 ----à 1 && 0 -----à0

4)凡是直接if(x),x为数或一个随后得到数字的表达式,只要数值不为0则为真。

If (4) printf(‘haha this is true\n’);

If (year%4==0 && year%100 !=0 || year%400==0)

printf(“闰年”)

★条件表达式:

Max=(a>b) ? a : b;

转换大小写:lower=(input>=’A’ && input <=’Z’) ? (input +32) : input ;

八:输入输出

由库函数提供的,C语言并没有。---à #include <stdio.h>

1)字符的:getchar(‘ ’)                   putchar(‘ ’)

eg: a=getchar()

putchar()

2)字符串:gets(“ … ”)                    puts(“ … ”)

3)格式化:scanf(格式输入)             printf(格式输出)

格式:d: 有符号的十进制整数,若为nd 则是n列输出

c: 字符格式 (nc)

int a=121;

printf("%c\n",a);       // y

s:字符串(数组):

scanf(‘%s’ , c)

printf(“%s”, “CHINA”);

f: float 与 double。默认输出6位小数。

(m.nf :一共m列,小数显示n列 【若为-m.nf则数左对齐】)

double a=1.0;

printf(“%f\n”, a/s);     // 0.333333

e: 指数形式输出

printf(“%e”,123.456);

scanf(格式控制,地址表列)注意事项:

scanf(“a=%f, b=%f”, &a, &b)   //&为地址符

此个在输入时:a=1, b=2

scanf(“a=%f b=%f”, &a, &b)

此个在输入时:a=1 b=2

scanf(“%c%c%c”,&c1,&c2,&c3);

此个在输入时:pdn

总之即是要与格式控制语句的格式相同

九:选择语句

1)  if

if(表达式){

//pass

}

else if(表达式){

//pass

}

//else if可多个

Else{

//pass

}

嵌套式://感觉可用于二分法

if(){

if(){

}

else{

}

}

else{

}

2)  switch

switch(grade){

case ‘A’:printf(“top student”);

case ‘B’:printf(“good student”);

.

.

.

}

十:循环结构

♦break

♦continue

1)       for

for(表达式1;表达式2;表达式3){

//pass

}

while True{      =     for(;;){    //只要中间语句为空,两边还可以用于记录信息

//pass                   //pass

}                                               }

e.g:

#include <stdio.h>

#include <stdlib.h>

int main(){

int i;

for(i=0;;i++){

if(i==10){

break;

}

else

printf("hehe\n");

}

system("pause");

return 0;

}

e.g:输出fibonacci数列前x数

int f1=1,f2=1,f3,i;

printf("%12d\n%12d\n",f1,f2);

for(i=1;i<10;i++){

f3=f1+f2;

printf("%12d\n",f3);

f1=f2;

f2=f3;

}

2)       while

while(表达式){

//pass

}

do{

//pass

}while(表达式)

十一:数组

♠数组中的每一个元素都是同一个数据类型!即是定义时的数据类型

1)  一维数组:

必须在定义时指定长度.   若n变量已近确定   可以int a[2*n];

初始化:int a[10]={可以初始化任意个元素,没有写的默认初始化为0}

e.g:a[10]={1,2,3,4,65};

*int a[]={1,2,3,4,5}   //即是又后面确定了为a[5]

e.g:冒泡排序:

每次从第i++元素开始对后面依次相邻两元素作比较并且调换位置

int i,j=1,a[10]={9,8,7,6,5,4,3,2,1,10},temp;

while(j<11){

for(i=0;i<10;i++){

if(a[i]>a[i+1]){

temp=a[i];

a[i]=a[i+1];

a[i+1]=temp;

}

}

j++;

}

for(i=0;i<10;i++){

printf("%d\t",a[i]);

}

2)二维数组:

元素顺序按行存取,每一行的元素存完后再存下一行。【连续地址】

初始化:

1)完全初始化:

int a[2][2]={ {1,2} , {3,4} };

int a[2][2]={ 1,2,3,4 };

2)部分初始化:

int a[3][4]={ {1} , {} , {0,2} };

e.g:打擂法找最大值:

int i,j,max=0,a[3][4]={{9,8,7,6},{5,4,3,2},{1,10,3,55}},column,row;

for(j=0;j<3;j++){

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

if(a[j][i]>max){

max=a[j][i];

column=j;

row=i;

}

}

}

printf("max=%d\ncloumn=%d\nrow=%d\n",max,column,row);

3)字符数组:

即每个元素都是一个字符的数组。

e.g:

char c[10]={‘c’ , ’ ‘ , ’p’ , ’r’ , ’o’ , ’g’ , ’r’ , ’a’ , ’m’ };

char c[10]=”c program”;

char triangle[2][3]={ { ‘ ‘ , ‘*’ , ‘ ‘} , { ‘*’ , ‘*’ , ‘*’ } };

★  C语言在每个字符数组的最后一个字符后加上一个’\0’作为结束符

char c[10] = {‘h’,’a’,’h’,’a’};  =  char c[10] = {‘h’,’a’,’h’,’a’,’\0’};

输入输出:

1)    scanf(“%s”,c);

printf(“%s”,c);

数组名即是数组头指针,所以scanf时不要加地址符:&

⊙: char str[13];   scanf(“%s”,str);

若在终端输入:how are you    则只会得到how

2)    gets (str);

puts (str);

注:gets与puts只能用于一个字符串数组的输入输出。而1)可以多个

不过用在初始化而为字符数组还不错:

e.g:char str[3][20];

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

gets(str[i])

}

一些内建字符串功能函数:

strcat (str1 , str2) : 把str2加到str1后面【需确保str1足够大】。

strcpy(str1 , str2) :          把str2复制到str1里面去。

strcmp(str1 , str2): 比较大小(返回值:1>2:正;1=2:0 ; 1<2 : 负)。

strlen(str): 返回字符串实际有效长度

e.g: char str[10] = “China”;

strlen(str)  -------à 5

e.g:统计一段字符串的单词个数(165)

char string[81];

int i,num=0,word=0;  //初始word为0是为了把首单词计算入

for(i=0 ; (c=string[i])!=’\n’ ; i++){

if(c==’ ‘)   word=0;

else if(word = = 0){

word=1;

num++

}

}

比较三个字符数组大小(167)

十二.函数:

**:函数需要定义在main函数前,这样在main函数里就不用申明了

函数申明:1)void型:不返回任何值

2)其他如int型:代表函数最后return的数据类型一般为int型

C语言中函数不能嵌套定义,但能够相互调用。

函数形参在调用时才临时分配存储单元并把实参的值传递给形参,调用完后释放形参。

嵌套调用:

int max2(int x, int y){

return(x>y? x:y);

}

int max3(int a,int b,int c){

int m;

m = max2(a,b);

m = max2(m,c);

}

递归调用:

e.g:hanoi问题(移盘子)

void move(int x,int y){

printf("%c--->%c\n",x,y);

}

//最终目的是把A盘移到C盘,

/*递归思路:

1):先让n-1个从A移到B盘

2):然后将A盘(最大的那个)移到C盘

3):再将B盘的n-1个移到C盘

*/

void hanoni(int n,char A,char B,char C){

if(n==1){

move(A,C);

}

else{

hanoni(n-1,A,C,B);    // 1)

move(A,B);            // 2)

hanoni(n-1,B,A,C);    // 3)

}

}

int main()

{

int num;

char A,B,C;

printf("input the number of disks:");

scanf("%d",&num);

hanoni(num,'A','B','C');

system("pause");

return 0;

}

//算法复杂度大概为2^n

数组作为形参的情况:

1)一维数组

可以不指定数组大小:float average(float array[ ] , int n){

}

在实际中一般用后面的n确定长度

2)二维数组:

第一维可以放空,第二维必须指定。

e.g:int max_value(int array[ ][4] , int n ){

//…

}

十三:变量的存储:

在c语言中,每一个变量与函数都有两个属性:数据类型(见后)与数据的存储类别( 指的是数据在内存中的存储方式【如静态存储与动态存储】 )。

左图为内存中可供用户使用的使用的存储空间。其分为三类。

数据分别存储在其中的静态存储区与动态存储区。

静态存储区:

全局变量

在程序开始执行时给全局变量分配存储区,程序执              行完毕才释放,在执行过程中占据固定的存储单元。

动态存储区:

1)  函数形参

2)  函数中没用static声名的变量(自动变量)

3)  函数调用时的现场保护与返回地址

在函数开始时分配动态存储空间,函数结束时就释放。地址是随机的,若先后调用两次同一函数,形参所用地址可能不同。

存储类别:

1)  auto变量(自动变量):

此为默认的可以省略的,与static对应。形参,复合语句中定义的局部变量,函数中的局部变量,若不声名为static则默认为auto,表明系统会动态的为他分配地址。(即在动态存储区)

e.g:

int f ( int a ){

[ auto ] int a;

}

2) static局部变量(静态局部变量):

有时希望在函数调用结束后其中的某些局部变量并不消失而保留原值。(即不释放存储单元,下一次访问时该变量已有值)。

e.g:

int f( int a ){

auto int b=0;

static int c=3;

b += 1;

c += 1;

return( a+b+c );

}

int main(){

int a=2 , i;

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

printf(“%d\n”,f(a));

return 0;

}

输出:7  8  9

C值依次都被记录了下来

★:虽然在函数调用结束后变量任然在,但是其他函数并不能够调用它,只有本函数才能够再次调用

◐◑:对于静态局部变量来说,如果不初始化,在编译时自动赋初值0或’╲n’

3:register变量(寄存器变量):

一般情况,变量(包括静态与动态存储方式)的值是放在内存中的,当程序要用到哪个变量时控制器发出指令将内存中该变量的值送到运算器中,运算完毕后再返回送到内存中。

而寄存器是在CPU中的,对于最频繁使用的变量可以使用此类型。

##:现在优秀的编译器会自动识别,不需要人为指定

综上:定义变量时分为三部分:存储类别 + 数据类型 + 变量名

e.g:static int a

关于全局变量在不同文件间的使用:P208

十四, 指针:

存储单元有两个概念:存储单元的地址与存储单元的内容

假设有变量i,系统分配地址为2000~2004的四个字节给变量i,实际上,程序是通过变量名i找到存储单元的地址2000~2004的【编译后已经将变量名转化为变量地址】。对于变量值的存取都是通过地址进行的。

此上对值的访问方式为直接访问。

而如果把变量i的地址存放放在另一变量中,然后通过该变量来找到变量i的地址从而访问i变量,此为间接访问。

int i , *i_pointer;

i_pointer = &i ;    # i_pointer即是指针变量

此时,i_pointer的值为i变量的起始值2000。而*i_pointer就是变量i 。*表示指向

## 一个变量的起始地址即称为该变量的指针。指针变量就是地址变量,用来存放地址。【注意区分指针和指针变量,指针是一个地址,而指针变量时存放地址的变量。】

int *i_pointer 读为指向int型变量的指针变量i_pointer

e.g:初级易错:

void swap(int *p1,int *p2){

int *p;

p=p1;

p1=p2;

p2= p;

}

int main(){

int a=1,b=2;

int *pointer_1 =&a,*pointer_2=&b;

if(a<b){

swap(pointer_1,pointer_2);

}

printf("%d\t%d\n",a,b);

system("pause");

return 0;

}

输出:1,2

只是完成了指针pointer_1与pointer_2的交换。变量a,b的值并没有改变。

e.g:指针为形参的函数交换变量

void swap( int *p1, int *p2){

int *temp;

*temp = *p1;

P1 = *p2;

P2 = *temp;

}

指针操作数组:

1)  一维数组:

!一维数组名就是数组的头指针。

int a[10];

int *p;、

初始化:

1)  p = &a[0];

2)  p=a;

用指针调用数组元素:

上面就是p指向了a数组头,用指针变量调用数组元素:*p表示a[0], 而*(p+i)则表示a[ i ],其实是依次移动指针到达a[i]变量的首字母从而实现调用值【编译后基于地址取值】。【小技巧:在遍历时常用p++最后到达p+n-1,不过需要注意的是遍历完后指针在数组末尾,最好需重新初始化p=a至数组首一面后面用混】。

e.g:

#include <stdio.h>

#include <stdlib.h>

/*等效表达

int main()

{

int a[10],i;

printf("input 10 numbers:");

for(i=0;i<10;i++){

scanf("%d",(a+i));

}

for(i=0;i<10;i++){

printf("%d",*(a+i));

}

printf("\n");

system("pause");

return 0;

}

*/

/*

int main()

{

int a[10],i,*p=a;

//    p=a;

printf("input 10 numbers:");

for(i=0;i<10;i++){

scanf("%d",(p+i));

}

for(i=0;i<10;i++){

printf("%d",*(p+i));

}

printf("\n");

system("pause");

return 0;

}

*/

/*

int main()

{

int a[10],i,*p=a;

printf("input 10 numbers:");

for(i=0;i<10;i++,p++){

scanf("%d",p);

}

p=a;     //上面的循环已经将p指向了a[9],故需要在此重置一下到a[0]

for(i=0;i<10;i++,p++){

printf("%d",*p);

}

printf("\n");

system("pause");

return 0;

}

*/

/*指针实现逆向输出的

int main()

{

int a[10],i;

printf("input 10 numbers:");

for(i=0;i<10;i++){

scanf("%d",(a+i));

}

for(i=9;i>=0;i--){

printf("%d",*(a+i));

}

printf("\n");

system("pause");

return 0;

}

*/

对于数组名作为函数参数的情况:

void func(int arr[] , int n){

}

等价于:

void func(int *arr, int n){

}

在主函数中,假设有a[10],调用时:func( a , 10);

原因:C语言编译都是将形参数组名作为指针变量处理的,在该函数被调用的时候,系统在func函数中建立一个指针变量arr用来存放从主调函数传过来的实参数组首元素的地址。

e.g:逆序输出

#include <stdio.h>

#include <stdlib.h>

// key:关于(n-1)/2对称交换即可

// int型时5/2=2(保留整数部分)

/*version 1:数组实现

void reverse(int a[],int n){

int i,temp;

for(i=0;i<=(n-1)/2;i++){

temp=a[i];

a[i]=a[n-i-1];

a[n-i-1]=temp;

}

}

int main()

{

int a[10],i;

printf("input some number:");

for(i=0;i<10;i++){

scanf("%d",(a+i));

}

reverse(a,10);

for(i=0;i<10;i++){

printf("%d",*(a+i));

}

printf("\n");

system("pause");

return 0;

}

*/

//version2:指针实现:

void reverse(int *p,int n){

int *j=(p+n-1),temp,i;     //主义:最后个元素指向时为 '+n-1'

//    j=p+n;

for(i=0;i<=(n-1)/2;p++,j--,i++){

temp=*p;

*p=*j;

*j=temp;

}

}

int main()

{

int a[10],i;

printf("input some number:");

for(i=0;i<10;i++){

scanf("%d",(a+i));

}

reverse(a,10);

for(i=0;i<10;i++){

printf("%d",*(a+i));

}

printf("\n");

system("pause");

return 0;

}

2)指针引用多维数组:

上图即为二维数组下的指针。

a[i] 即 a+i 以及*(a+i)也成了一个指针变量,指向第i行的起始元素地址。

要第i行第j列元素的地址:a[i]+j 或 *(a+i) + j

故第i行第j列元素的值为:*( * ( a + i ) + j )

遍历输出方式一:

int a[2][2] = { { 1 , 2 } , { 3 , 4 } };

int *p;

for( p = a or a[0] ; p<a[0]+4 ; p++){

printf(“%d\t”, *p );

}

由于多维数组内存以行连续,故可以按以上输出。

About: int (*p)[n]

定义p为指针变量,指向包含四个整型元素的一维数组。

注:p与*p都指向数组头。若等效于a[n]来看,*p相当于a,而不是p!!!

e.g:

int a[4] = {1,2,3,4} , (*p)[4];

p = a;

则(*p)[3] or *(*p+3)即是a[ 3 ]

e.g2:

int a[2][3] = {{1,2,3},{4,5,6}} , (*p)[3] ;  //注意(*p)[3]中的3!

p=a;

若要访问a[i][j] , 用 *(*(p+i)+ j );  *(p+i)即是a[i]

注意区别和一维数组时先通过*p再横移动,而此地用*(p+i)找到再横移,这是因为二维数组a[i]也是指针的1缘故。

e.g:

找出三个班每个班四个学生的平均值和最大值:

#include <stdio.h>

#include <stdlib.h>

void average(float *p,int n){

float *p_end;

float sum=0,aver;

p_end=p+n-1;

for(;p<=p_end;p++){

sum=sum+(*p);

}

aver=sum/n;

printf("average=5.2%f\n",aver);

}

void search(float (*p)[4],int n){

int i;

printf("the score of No.%d are:\n",n);

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

printf("%5.2f\t",*(*(p+n)+i));

}

printf("\n");

}

int main()

{

float score[3][4]={65,67,70,60,80,87,90,81,90,99,100,98};

average(*score,12);

search(score,2);

system("pause");

return 0;

}

上一篇:Sublime Text 3的快捷键


下一篇:SQL Server-表表达式基础回顾(二十四)