C

3小时学会C语言精华

Master C in three hours

Posted by xuepro on November 3, 2017

1. 程序:=数据(变量/常量)+运算(符)

  • 数据包括变量和常量,变量必须说明类型。类型规定了变量占据内存的大小

  • 运算符有:算术运算符、比较运算符、逻辑运算符、赋值运算符等。

   算术运算符: +-*/%(求余数)、++(自增1)、--(自减1
   比较运算符:><>=<=!=(不等于)
   逻辑运算符:&&(逻辑与)||(逻辑或)!(逻辑非)
   位运算符::&()|()^()~
   赋值运算符:=+=-=*=/=%=&=^=^=

运算符具有不同优先级,记不住优先级,可以用()保证正确运算次序。

http://www.runoob.com/cprogramming/c-operators.html

/* 这里是程序注释,用于对程序进行说明,
     比如下面的是求圆的面积程序。     
*/

/*输入输出头文件,包含很多输入输出函数如输出函数printf的函数说明(函数规范) */

#include <stdio.h>  


/* #define 用于给常量3.1415926起个名字PI,称为宏定义,
   PI就是一个宏常量*/
#define PI 3.1415926

int main(){
   float r = 4.5; /* 定义了一个叫做r的类型是float(单精度实数类型)*/
   float area = Pi*r*r/2; /* 计算圆面积,初始化变量area */
   printf("半径是%f的圆的面接是%f\n",r,area);   
}

注:

   1) /* 和 */内容是程序注释,不是程序指令(语句),仅仅用于对程序进行说明。

   2) main是自定义的函数,程序总是从着唯一的main这个主函数开始执行,用左右{}括起来的程序语句称为程序块。

   3) printf是一个C语言提供的实现好函数,它的说明在头文件stdio.h里,所以要包含这个头文件。

2. printf和scanf函数

printf是格式化输出函数,其格式为:

   int printf("引号括起来的格式串",输出项1,...);    

格式串中包含3种字符:

1)普通字符,printf函数会直接输出

2)\开头的字符比如: ‘\n’表示“换行符”、’\t’表示“制表符”,称为“转义字符”printf函数不会输出它们,如果是’\n’,输出会换一行,如果是’\t’,会输出几个空格,还有其他各种转义字符。

3)%开头的叫做“格式符”,表示”格式串”后的输出项按照什么数据类型进行输出,有几个输出项,格式串中就有几个格式符。常见的格式符有:

   %c ,表示对应输出项是字符char
   %d ,表示对应输出项是整数类型int
   %f ,表示对应输出项是单精度实数类型float
   %lf ,表示对应输出项是双精度实数类型double
   %s ,表示对应输出项是一个字符串,即一串字符

scanf是格式化输入函数,其格式为:

   int scanf("引号括起来的格式串",&输入项1,...);    

注意:scanf的输入项前必须有一个取地址运算符&,表示传给scanf的这个输入项的地址。

#include <stdio.h>

#include <math.h>  /*包含了求开方函数sqrt的定义*/

/*从键盘输入方程系数,输出方程的2个根*/
int main(){
  double a,b,cdelta,x1,x2;
  scanf("%lf %lf %lf",&a,&b,&c);
  delta = b*b-4*a*c;
  x1 = (-b+ sqrt(delta) )/(2*a); /*注意用圆括号()保证正确的运算程序*/
  x2 = (-b- sqrt(delta) )/(2*a);
  printf("方程%lf x^2 + %lf x + %lf的根为%lf,%lf\n",a,b,c,x1,x2);
}

3. 数组

相同类型元素的顺序集合,所有相同元素放在一块连续存储空间中,定义格式为:

格式

 类型   数组名[大小]

 类型   数组名[大小] = 初始化式

类型   数组名[] = 初始化式

例如:

int  arr[10];   /*大小为10的数组,其中的数据元素没有初始值*/
int  arr[10] = {0,1,2,3,4,5,6,7};   /*大小为10的数组,其中前8个数据元素有初始值*/
int  arr[] = {0,1,2,3,4,5,6,7};  /*大小为8的数组,每个元素有初始值*/

可以通过 “下标访问运算符[]” 去存取(读写)单独的数据元素。

int  arr[10];
arr[0] = 1;  /*数组下标从0开始,第一个数组元素为arr[0]*/
arr[9] = 10; /*第10个元素a[9]*/
printf("%d,%d\n",arr[0],arr[9]);

多维数组,可以通过多个下标去存取其中的数据元素,其定义格式类似于一维数组。比如二维数组:

/*定义了一个二维数组,
第一维(称为行)大小是3,
第二维(称为列)大小为4*/
int a[3][4] = {  
   {0, 1, 2, 3} ,   /*  行下标为0的第1行的初始化 */
   {4, 5, 6, 7} ,   /*  行下标为1的第2行的初始化  */
   {8, 9, 10, 11}   /*  行下标为2的第3行的初始化  */
};

/*定义了一个二维数组,
第二维(称为列)大小为4,
第一维(称为行)大小是2, 
行数由初始化式子中的个数确定
*/
int matrix[][4] = {  
   {0, 1, 2, 3} ,   /*  行下标为0的第1行的初始化 */
   {4, 5, 6, 7} ,   /*  行下标为1的第2行的初始化  */  
};

/*修改第3行第4列(下标为[2][3])的元素的值*/
a[2][3] = 23;

字符串

结尾带有“结束字符”即’\0’的字符数组。

#include<stdio.h>

#include <string.h>  /*字符串处理函数的头文件*/

int main(){
   char str[] = {'h','e','l',''l','o','\0'}; 
   str[0] = 'L'; /*通过下标修改你某个字符*/
   printf("%s\n",str);  /* 格式符%s表示str是一个字符串 */

   /*用字符串函数strlen获得str的字符个数。结束字符不算在内的*/
   printf("str字符串的字符个数是%d\n",strlen(str);

   str[5] = ','; /*字符数组str不再是一个C语言的字符串了,因为结束字符不是'\0'了 */
   printf("%s\n",str);   /*错!结果是不可预知的*/
}

4. 循环语句

for系列和while系列两种

 for (init初始表达式; test测试表达式;  post_proc后处理表达式)
 {
     循环体程序块
 }

含义: 先执行init初始化表达式,然后重复下列循环:

   如果"test测试表达式"真 -> 执行body程序块 -> post_proc后处理表达式

直到”test测试表达式”不真。

#include <stdio.h>

int main{ 
   int arr[6],i;
   for(i=0; i< 6 ;i++) /*每次i++就使i增加1*/
      arr[i] = 1 +2*i;

   for(i=0; i< 6 ;i++)
      printf("%d\n",arr[i]); 
}  
  while (condition表达式)
  {
      循环体程序块
  }

含义:只要while里得到condition表达式是真的,就一直重复执行”循环体程序块”。

#include <stdio.h>

int main{ 
   int arr[6],i=0;
   while(i< 6){
      arr[i] = 1 +2*i;
      i++; 
   }

   i = 0; /*重新赋值为0*/
   while(i< 6){
      printf("%d\n",arr[i]);
      i++;  
  }
}  

可以用关键字break跳出循环体

#include <stdio.h>

int main{ 
   int arr[6],i=0;

   while(1){ /*该条件始终不为0*/
      arr[i] = 1 +2*i;
      i++; 
      if(i==6)   /*关键字if(如果)判断i<6是否为真*/
        break; /*跳出while循环*/
   }

   i = 0; /*重新赋值为0*/
   while(1){ /*该条件始终不为0*/
      printf("%d\n",arr[i]);
      i++;  
      if(i==6) 
        break; /*跳出while循环*/
  }
}  

5. 条件语句

包括if系列和switch语句

if语句格式:

    if (表达式) 
    {
       ...程序块
    }

含义: 如果“表达式_1为真”,则执行其中的程序块;

 ```
if (表达式_1) 
{
   ...程序块1
}
else
{
   ...程序块2
} ```

含义: 如果“表达式_1为真”,则执行程序块1;否则执行程序块2

    if (表达式_1) 
    {
       ...程序块1
    }
    else if (表达式_2)
    {
       ...程序块2
    }
    else
    {
       ...其他程序块
    }

含义: 如果“表达式_1为真”,则执行表达式_1对应的“程序块1”; 否则如果“表达式_2为真”,就执行表达式_2对应的“程序块2”; 否则就执行其他情况下的“其他程序块”。

#include <stdio.h>         

/* 根据输入整数是否正负等情况,输出不同信息!*/
void main(){
    int n;
    scanf("%d", &n);
    if ( n<0 ) 
       printf (" %d is a 负整数!\n" ,n) ;
    else if ( n==0 )
       printf (" %d is = 0!\n" ,n) ;
    else  printf (" %d is a 正整数!\n" ,n) ;
}

switch语句格式:

 switch (表达式)
 {
   case 常量_表达式_1:  
      ...程序块1..
       break;
   
   case 常量_表达式_2:   
      ...程序块2...
        break;
   ...
   default:        
      ...默认程序块..
        
 }

含义: 就是当里面的switch表达式等于其中的常量时,就执行其中的程序块, 然后用break跳出整个switch语句,如果不等于任何一个常量,就执行默认(default)里的程序块。

#include<stdio.h>

void main( ){
    char command;          
    command = getchar( );          
    switch( command){
      case ' W ' :
         printf( " welcome to come here ! \n " ); 
         break;
      case 'P':
         printf( " I print some information  for you \n " ); 
         break;
      default:
         printf(" You pressed other key \n ");
         break;
      }               
}      

6. 函数

对数组中所有数求和

#include <stdio.h>

int main{ 
  int arr[6] = {1,2,3,4,5,6},
    arr2[8],i,s;
   
  for(i=0; i< 6 ;i++) 
      arr[i] = 1 +2*i;

  for(i=0; i< 8 ;i++) 
      arr[i] = (i+1)*(i+1);

   i = 0; 
   for(i=0; i< 6 ;i++) 
      printf("%d\n",arr[i]); 
   printf("\n"); /*换一行*/

   i = 0; 
   for(i=0; i< 8 ;i++) 
      printf("%d\n",arr2[i]); 
   printf("\n"); /*换一行*/ 

   /*对arr中所有数求和*/ 
   s = 0;
   for(i=0; i< 6 ;i++) 
      s += arr[i] ; /* 等价于 s = s+arr[i]; */
   printf("the sum of arr is %d\n",s); /*换一行*/

    /*对arr中所有数求和,复制粘帖前面的代码,稍作修改*/ 
   s = 0;
   for(i=0; i< 8 ;i++) 
      s += arr2[i] ; /* 等价于 s = s+arr[i]; */
   printf("the sum of arr2 is %d\n",s); /*换一行*/
}  

假如有很多个数组要经常求和和输出,可以复制相应代码,做一些调整.

但我们还有更好的方法:可以给这些“求和或输出代码块”起一个名字,然后多次调用这里面的语句,就成了所谓的“函数”。

自己定义个叫做Print的函数,不能与已有函数如printf同名 Print函数用圆括号说明其作用的参数是数组a和数组大小n 参数名a有[],表示a不是一个整数,而是一个数组 Print前的void是这个函数的返回值的类型,因为这里 不需要返回,所以类型为所谓的“无类型”类型void。

#include <stdio.h>



void Print(int a[],int n){  
   i = 0; 
   for(i=0; i< 6 ;i++) 
      printf("%d\n",arr[i]); 
   printf("\n"); /*换一行*/
}

/*对arr中所有数求和,
sum的返回类型为int,表示返回的是一个整数。
*/ 
int sum(int a[],int n){ 
   s = 0;
   for(i=0; i< 6 ;i++) 
      s += a[i] ; /* 等价于 s = s+a[i]; */
   return s; /*用return关键字返回结果s,其类型为int*/
}

int main{ 
  int arr[6] = {1,2,3,4,5,6},
    arr2[8],i,s;
   
  for(i=0; i< 6 ;i++) 
      arr[i] = 1 +2*i;

  for(i=0; i< 8 ;i++) 
      arr[i] = (i+1)*(i+1);

   /*调用自定义的Print函数,将实际参数arr和大小6
   分别传给被调用函数Print的形式参数a和n*/
   Print(arr2,6);

   Print(arr2,8);

   /*调用函数sum(arr,6),对arr中所有数求和,
     返回结果给main函数的变量s。 */ 
   s = sum(arr,6);  /
   printf("the sum of arr is %d\n",s); /*换一行*/
   
   /* sum(arr2,8) 返回值直接作为printf函数的输出项*/
   printf("the sum of arr2 is %d\n",sum(arr2,8)); 
}  

这种函数“一次定义、多次调用”的行为叫做“代码复用”,可以大大提高编程效率、简化代码。C程序中经常调用各种C函数库提高程序开发效率,如标准库的printf等函数。

函数堆栈

堆栈是一个“后进先出,先进后出”的数据结构,比如一叠碗只能从最上面放入碗,也只能从最上面拿走一直碗。其插入和删除元素只能在一端进行。又比如一头堵死的小巷子只能容纳一个汽车进出,则进出汽车必须按照“后进先出,先进后出”才能正常进出。

每个程序有一个函数运行堆栈,栈顶保存的是保持当前正运行函数的局部变量(包括形式参数)。一个函数开始执行,其局部变量入栈,当它执行结束后,局部变量就出栈。

递归函数

调用自身的函数。如

int fc(int n){
   if(n==1)
     return 1; /*函数结束,直接返回*/
   return n*fc(n-1);
}

int main(){
  int a = 6;
  printf("%d\n", fc(a));
  a  = 9;
  printf("%d\n", fc(a));
}

7. 指针

#include <stdio.h>

int main(){
  int a = 3,b=4;
  int *p = &a; /*取地址运算符& 用于获取a的地址,
               放入整型指针(int *)变量p */
  printf("%d\t%d\t%d\t%d\n",b,a,&a,p);

  b = *p; /* 取内容运算符*用于获得一个指针指向的变量(内存),
         因此*p 就是a  */
  printf("%d\t%d\n",a,b);

  p = a; /*错!a的类型是int(整型),而p的类型是int *(“整型指针”类型)*/
}

指针变量存储的是地址(指针),如p存储的是a的地址。当然可以存储数组的第一个元素的地址(也称为数组的地址,数组名就是数组的地址)。

#include <stdio.h>

int main(){
  int a = 3,b,arr[] = {2,3,4,5,6},i;
  int *p = &a; /*取地址运算符& 用于获取a的地址,
               放入整型指针(int *)变量p */
  printf("%d\t%d\t%d\n",b,a,&a,p);

  b = *p; /* 解引用运算符*用于获得一个指针指向的变量(内存),
         因此*p 就是a  */
  printf("%d\t%d\n",a,b);

  p = arr; /* 等价于 p = &arr[0] */
  printf("%d\t%d\n",arr[0],*p);

  /* arr[i]等价于 *(arr+i)
     p[i]等价于 *(p+i)
   */
  for(i = 0 ; i<5;i++)
    printf("%d\t%d\t%d\t%d\n",arr[i],p[i],*(arr+i),*(p+i));
}

注意:这里我们学习了2个运算符:取地址&,解引用*请体会理解其含义!

8. 结构

假如某种类型的对象有多个属性,我们可以将这些属性组合起来,定义一个新的数据类型用于刻画这种类型所有对象的这些共同属性,比如图书有:书名、出版社、作者、出版日期等属性,学生有:姓名、性别、成绩属性,可以用关键字struct来定义相应的结构类型。

结构类型定义格式:

   struct 结构类型名{
      成员属性
   };

/*定义一个叫做struct Book的结构类型*/
struct Book{
   char name[100];
   char publisher[50];
   char author[10];
   char date[10];
};

/*定义一个叫做struct student的结构类型*/
struct student{
   char name[10];
   int sex;   /*1表示男,0表示女*/
   double score; /*学分*/
};

#include <stdio.h>

/*可以定义结构类型的变量*/
int main(){
  /*定义struct student类型的变量stu,
    stu就是表示一个具体学生信息的变量了.*/
  struct student stu;

  /*调用字符串拷贝函数strcpy将常量字符串拷贝到stu.name,
    访问stu的成员name用成员访问运算符'.'
   */
  strcpy(stu.name,"LiPin"); 
  stu.sex = 1;
  stu.score = 78.5;
  printf("%s\t%d\t%lf\n",stu.name,stu.sex.stu.score);  
}

注: strcpy是C字符串的一个拷贝操作,用于将第二个字符串拷贝给第一个字符串.

#include<stdio.h>
#include<string.h>
#define MAX 20

int main(void)
{
   char a[MAX]="abc";
   char b[MAX]="abcdefghi";
   strcpy(a,b);
   puts(a); /* puts函数用于输出字符串a*/
   puts(b);
} 

结构指针

#include <stdio.h>

int main(){
  /*定义struct student的变量stu */
  struct student stu;

  /*定义struct student*的变量p*/
  struct student * p; 
  p = &stu;  

  /* *p就是stu */

  strcpy((*p).name,"LiPin"); 
  (*p).sex = 1;
  (*p).score = 78.5;
  printf("%s\t%d\t%lf\n",(*p).name,(*p).sex,(*p).score); 

  /* 还可以用间接访问运算符->去访问p指向的就结构类型变量stu*/ 
  strcpy(p->name,"LiPin");
  p->score = 90.5;
  printf("%s\t%d\t%lf\n",p->name,p->sex,p->score);  
}

9. typedef

给已有类型起一个新名字(别名)。新类型实质就是原有类型。如

typedef unsigned int size_t;  /*无符号整型类型unsigned int
                               的别名为size_t*/
size_t j = 4;   /*size_t类型的变量j*/

我们可以用typedef给一个无名结构类型起一个名字,这也,以后定义变量时机不需要每次都要在结构类型名前写”struct”关键字了。如:

typedef
struct {  /*无名结构*/
   char name[10];
   double score;
}student;

int main(){
  student stu;  /* 类型student前不需要关键字struct了 */
  student *p = &stu;
  strcpy(stu.name,"LiPing");
  stu->score = 80.5;
  printf("%s,\t%lf",(*p).name,p->score);
}

10. 动态内存分配

静态数组一旦定义,程序运行时就不能再修改其大小

  int arr[100];
  struct student students[80];

缺点:

  • 万一程序需要存储的整数超过100或学生数目超过80怎么办?
  • 另外,一开始分配足够大的空间,会造成空间浪费!假如大多数情况下学生数目不超过30人,程序固定分配80个学生的数组,就是浪费!

改进方法是采用动态内存分配。程序运行过程中,通过调用动态内存分配函数如(malloc,calloc,realloc)向操作系统申请一块大小合适的内存。这块内存用完后应归还给操作系统,即用内存释放(free)。 这些函数可以在 stdlib.h 头文件中找到。

void *calloc(int num, int size);
  在内存中动态地分配 num 个长度为 size字节
  的连续空间,并将每一个字节都初始化为 0
  返回分配到的内存块的地址
  所以它的结果是分配了 num*size个字节长度
  的内存空间,并且每个字节的值都是0

void *malloc(int num); 
  在堆区分配一块num个字节的内存块,用来存放数据。
  这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。

void *realloc(void *address, int newsize); 
  该函数分配一块newsize个字节的内存块,
  并将address指向的旧内存块内容拷贝到新内存块中,
  返回新内存块的地址

void free(void *address); 
该函数释放 address 所指向的内存块(内存空间)。
#include <stdio.h>

#include<stdlib.h>

int main(){
  int ni;
  scanf("%d",&n);  /*由用户输入分配的空间大小*/

   /*分配课存放n个整数的连续内存块,
   sizeof函数用于获得int类型变量
   占用内存大小(一个int变量占用多少字节)*/ 
     
  int *p = (int *)malloc(n*sizeof(int));

  /*给空间的每个整数赋值*/
  for(i = 0 ; i<n;i++)
     p[i] = 2*i+1;
  
  for(i = 0 ; i<n;i++)
     printf("%d\n",p[i]);  

  free(p); /*用完要释放占用的内存*/ 
}

比如,我们可以用动态内存分配实现C语言已经实现好的字符串拼接strcat函数。

strcat() 函数用来连接字符串,其原型为:
    char *strcat(char *dest, const char *src);

先看一下该函数的使用

#include <stdio.h>

#include <string.h>  /*字符串处理函数的头文件*/

int main (){
    char str[80]; /*字符数组用于存放字符串*/
    strcpy (str,"these ");
    strcat (str,"strings ");
    strcat (str,"are ");
    strcat (str,"concatenated.");
    puts (str);
    return 0;
}

我们自己来实现一个模拟标准库strcat同样功能的Strcat

char* Strat(char *dest,const char *src){
    char *p,*q;
   
   int len1 = strlen(dest),len2 = stelen(src);

   /*多分配一个字符,用于存放最后的结束字符'\0' */
   q = (char *)malloc(len1+len2+1);
   if(q==0){ /*内存范培失败,返回值为0即空指针*/
      return 0;  /*我们也返回0*/
   }

   free(dest); /*释放原来的dest的空间*/
   dest = q;   /* dest存储新空间地址,称为指向新空间*/

   p = dest; /*第一个字符开头*/   
   whle( (*p)!= '\0' ){  /* *p不是结束字符*/
      *q = *p ;
      p++; q++;
   }

   p = ssrc; /*第2个字符开头*/
   whle( (*p)!= '\0' ){  /* *p不是结束字符*/
      *q = *p ;
      p++; q++;
   }

   return dest;
}

/* 测试我们自己写的StrCat函数*/
#include <stdio.h>

#include <string.h>  /*字符串处理函数的头文件*/

int main (){
    char str[80]; /*字符数组用于存放字符串*/
    strcpy(str,"these ");
    Strcat(str,"strings ");
    Strcat(str,"are ");
    Strcat(str,"concatenated.");
    puts (str);
    return 0;
}

支付宝打赏 微信打赏

您的打赏是对我最大的鼓励!