DS

数据结构-C语言复习

review of C

Posted by xuepro on November 12, 2017

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

运算符对数据进行运行构成了“表达式语句”,程序由一系列“表达式语句”构成。 表达式语句可分为:普通的表达式语句、控制语句和函数调用语句。 如求一元二次方程的根的程序:

#include <stdio.h> /*包含了输入输出的函数如printf*/

#include <math.h> /*包含了求平方根的函数sqrt*/

int main(){
   float a,b,c,delta;
   scanf("%f %f %f",&a,&b,&c);
   delta = sqrt(b*b-4*a*c);
   if(delta<0) 
     printf("方程没有根\n");
   else if(delta==0) 
      printf("方程的根是%f\n",(-b+delta)/(2*a));
   else 
      printf("方程有2个根是%f\t%f\n",(-b+delta)/(2*a)
        ,(-b-delta)/(2*a));
}

2. 函数是一个命令的程序块

如果一个语句块(多个语句)可以被多次使用,如上述求方程的根、printf函数的语句块,则可给这个语句块起一个名字,叫做函数。课避免多次重复写相同的代码。

比如,要判断一个整数是否是质数的代码如下:

/*假设n不是一个质数,则n课表示成2个整数a、b的乘积:
  n = a*b,其中1<a<b<n 。其中那个小整数a必定是
  介于2和n/2之间的整数。
  因此,我们只要检查2和n/2之间的每个整数
  看看是否能整除n,就能判断n是否是质数了*/

  int main(){
     int n,a;  scanf("%d",%n);
     int is_prime = 0;
     for(a = 2; a<n/2; a++)
        if(n%a==0){
          is_prime  =1;
          break;
        }
     if(is_prime==1)
        printf("%d是一个质数\n",n); 
     else
        printf("%d不是一个质数\n",n);  
  }

假如程序中需要经常判断一个整数是否是质数,则可以复制粘贴上述代码。

int main(){
     int n,a;  scanf("%d",%n);
     int is_prime = 0;
     for(a = 2; a<n/2; a++)
        if(n%a==0){
          is_prime  =1;
          break;
        }
     if(is_prime==1)
        printf("%d是一个质数\n",n); 
     else
        printf("%d不是一个质数\n",n); 

     scanf("%d",%n);
     is_prime = 0;
     for(a = 2; a<n/2; a++)
        if(n%a==0){
          is_prime  =1;
          break;
        }
     if(is_prime==1)
        printf("%d是一个质数\n",n); 
     else
        printf("%d不是一个质数\n",n);
     
     scanf("%d",%n);
     is_prime = 0;
     for(a = 2; a<n/2; a++)
        if(n%a==0){
          is_prime  =1;
          break;
        }
     if(is_prime==1)
        printf("%d是一个质数\n",n); 
     else
        printf("%d不是一个质数\n",n);   

  }

如此代码会变得很臃肿,我们可以给判断质数的代码块起一个名字, 即变成一个函数,这个函数输入的是一个需要判断是否是质数的整数参数n。

void isPrime(int n){
     int a,is_prime = 0;
     for(a = 2; a<n/2; a++)
        if(n%a==0){
          is_prime  =1;
          break;
        }
     if(is_prime==1)
        printf("%d是一个质数\n",n); 
     else
        printf("%d不是一个质数\n",n);         
}

int main(){
     int n; 

     scanf("%d",%n);
     isPrime(n);

     scanf("%d",%n);
     isPrime(n);

     scanf("%d",%n);
     isPrime(n);
}

这样一来,代码就简洁多了,也节省了敲代码的工作量。函数极大地提高了编程效率!

3. 数组、指针

数组定义:声明其数据元素的类型和数组的大小。如:

int arr[10] = {0, 1, 2, 3} /* 10个int的数组,其余为 0 */

/*二维数组*/
int M[4][5] = {
  { 10, 5, -3},  /*第0行*/
  {9, 0, 0 },  /*第1行*/
  {32, 30, 1}, /*第2行*/
  {0, 0, 8}  /*第3行*/
};

/*前5个是第0行,依次类推*/
int N[4][5] = { 
  10, 5, -3,0,2,   9, 0, 0 ,   32, 30, 1,  0, 0, 8 
};

/*可以用下标访问数组的元素*/
printf("%d\t%d",arr[4],M[2][3]); /* 转义字符序列\t表示制表符,printf遇到它输出空几个空格*/

假如有一个int变量a,我们可以通过“&取地址运算符”得到a的地址&a,可以将这个地址放入一个int *类型的变量p中:

  int * p = &a;
  printf("%d\t %d",*p,a); // *p得到p指向的对象,因此*p就是a

我们可以将数组的地址(第一个元素的地址)放在一个指针变量中。如可以用 &arr[0]得到数组arr的地址, 实际上数组名就是数组的地址,因此:

  int *p;
  p = &arr[0];
  p = arr; 

可以通过++或+=n运算符将指向偏移。

/* 对数组元素求和 */
int arraySum (int array[] , const int n) {
   int sum = 0, *ptr;
   int * const arrayEnd = array + n;
   for (ptr = array; ptr < arrayEnd; ++ptr)
     sum += *ptr;
   return sum;
}

等价于

/* p[i]等价于 *(p+i) */

/* 对数组元素求和 */
int arraySum (int array[] , const int n) {
   int sum = 0
   for (int i ; i<n; i++)
     sum += ptr[i]; /* p[i]等价于 *(p+i)*/
   return sum;
}

4. 字符串

C语言的字符串就是带有结束字符’\0’的字符数组,但字符数组不一定是字符串。如:

  char str[20] = {'H','e','l','l','o'}; /*字符数组但不是字符串*/
  char str2[20] = {'H','e','l','l','o','\0'} /*字符串*/
  /*strlen是查询字符串长度的C语言函数,需要包含头文件<string.h>
  */
  printf("%d\t%d",strlen(str),strlen(str2)); /*strlen(str)的结果不可预期*/

C语言提供了很多处理C字符串的函数,这些函数都在头文件中声明了。如

/*src拷贝到dst,返回dst的指针*/
 char *strcpy(char *dst, const char *src); 
/*src拼接到dst后面,返回新的字符串指针*/
 char *strcat(char *dst, const char *src); 

实际上,我们完全可以自己实现这些C语言的库函数,如:

/* 计算字符串的长度 */
int stringLength(const char *str) {
   const char *cptr = str;
   while (*cptr)
     ++cptr;
   return cptr-str;
}

/*to指向的字符数组需要先分配足有的空间*/
void copyString(char *to, char *from) {
  for ( ; *from != ’\0; ++from, ++to )
    * to = *from;
  *to = ’\0;
}

练习: 请自己实现一下strcat的功能。

5. 结构、指针

结构就是将一个概念或实体的相关的属性组合在一起,程序员通过关键字struct定义一个结构类型描述一个概念或实体,比如定义一个叫做struct student的结构类型用来描述学生这个概念,正如int描述整数这个概念一样。

我们可以定义结构类型的变量,正如定义一个int型变量一样。

  • 通过.(点)运算符访问结构变量的成员
  • 通过->(箭头)运算符访问“结构变量指针”指向的“结构变量的成员”
struct student{
   char name[10];
   double score;
};

int main(){
  int a = 3;
  int *q = &a; //p存储a的地址,称为指向a
  *q = 5;
  printf("%d\t%d",a,*q);

  struct student stu;
  struct student *p;

  /*通过.(点)运算符访问结构变量的成员*/
  strcpy(stu.name,"LiPing");  
  stu.score = 80.5;
  printf("%s,\t%lf",stu.name,stu.score);
  p = &stu;

   /*通过->(箭头)运算符访问“结构变量指针p”指向的“结构变量的成员”
  strcpy(p->name,"ZhangWei");
  p->score = 60.5;
  printf("%s,\t%lf",p->name,p->score);

   /*--- *p 就是stu --- */
  strcpy((*p).name,"ZhangWei"); 
  (*p)score = 60.5;
  printf("%s,\t%lf",(*p).name,(*p).score);

}

6 typedef

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(p->name,"LiPing");
  stu.score = 80.5;
  printf("%s,\t%lf",(*p).name,p->score);
}

7. C++的单行注释//

除了C语言的块注释 /**/外, C++还提供了对一行进行注射的//

//这是一个hello world程序

int main(){
  cout<<"hello world";
}

8. C++的输入输出对象:cin、cout

我们可以用C++的输入输出对象:cin、cout来代替C语言的scanf、printf,再也不用为C语言输入输出格式化规则烦恼了。因为cin、cout能自动识别输入输出的变量类型。

这是一个关于scanf、printf的C语言程序

#include <stdio.h>

int main(){
  char ch;
  int a; 
  double d;
  char str[10];
  scanf("%c %d %lf %s",&ch,&a,&d,str);
  printf("%c %d %lf %s",ch,a,d,str);
}

而C++通过对输入流对象cin、输出流cout分别调用输入运算符»、输出运算符«。数据将像流水一样自动流进流出变量。

cin、cout分别代表键盘和屏幕对象。

#include <iostream> //C++的标准输入输出流头文件

#include <string>  //C++的字符串类型string的头文件

/*为避免不同库(程序之间)出现名字冲突(同名),
  C++引入了名字空间namespace,即名字是属于某个名字空间的
  如同不同单位出现同一个人名一样,可以通过单位名区分开来
*/
using namespace std; //string,cin,cout等都定义在名字空间std中

int main(){
  char ch;
  int a; 
  double d;

  string str;   // C++的字符串类型不需要规定大小

  cin>>ch>>a>>d>>str;  //数据从cin流入到变量中

  cout<<ch<<a<<d<<str; //数据从变量流出到cout中
}

9. C++的动态内存分配

C语言通过malloc等分配内存,都需要将返回的void指针类型(void *)转化为变量的特定类型指针。而C++用于内存分配的运算符new返回的内存块地址类型会自动识别,C++用于内存分配的new运算符和释放内存块的delete还可以自动调用类的构造和析构函数对对象进行初始化和销毁。更加方便安全。

#include <iostram>

int main(){
  int *p = new int;     /*分配1个整数的空间, 
  类似于C的: int * p = (int *)malloc(sizeof(int))*/

  int *q = new int[8]; /*分配8个整数的空间
  类似于C的: int * q = (int *)malloc(8*sizeof(int))*/

  *p  =3;
  *q = 4;
  *(q+1) = 5;
  *(q+2) = 7;
  *(q+5) = 8;
  q[3] = 6; //q[3]等价于 *(q+3)

  *(p+1) = 1; //错:p+1指向的内存块不属于你的程序!

  cout<<*p<<"\n\n"; 
  for(int i = 0 ; i<8;i++)
     cout<<q[i]<<"\n";

  //用delete释放用new分配的内存块

  delete p; // 释放单个元素内存块

  delete[] q; //释放多个元素内存块

  return 0;
}

练习:请实现一个用单链表存储学生信息的程序,具有:输入、输出、插入、删除、查找等功能。提示,课一定下述结构类型:

  typedef struct{
     string name;
     double score;
  }student;

  typedef struct lnode{
     student data;
     struct lnode *next;
  }LNode;  

10. C++的引用

: 这部分内容是C++的,为了下面的数据结构课程做准备以便简化函数的参数传递

在变量名前加一个修饰符& (注意这不是取地址运算符),说明这个变量是引用变量,也就是说这个变量将引用其他变量,是其他变量的别名! 有点类似typedef给已有类型其一个别名一样,这是给已有变量起一个别名,也就是这个引用变量和被引用的变量是同一个变量。

int main(){
  int a = 3;
  int &b = a;  /*定义引用变量b,它引用的是a,
               因此,b就是a
               也就是b和a指示的是同一块内存。*/
  b = 10;
  printf("%d\t%d\n",a,b);
}

假如写一个交换变量的代码

int main(){
  int a = 3,b=4;

  int t = a; a = b; b = t;

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

我们看到a,b的值发生了交换。

现在,我们将上述交换代码变成一个独立的函数

void swap(int x, int y){ 
  int t = x; x = y; y = t; 
}
int main(){
  int  a = 3,b=4;

  swap(a,b);

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

为什么没有发生交换? 这是因为C语言的参数传递是“传值”(实际参数的值拷贝给形式参数,实际参数和形式参数占据各自独立的内存块)。尽管在swap函数中x,y确实发生了交换,但和main函数的参数a,b毫不相干,回到main函数后,swap函数的参数已经销毁。

如何使得a,b交换呢? 可以将a,b的地址(指针)作为参数传给swap。

/*通过x,y对它们指向的数据进行修改*/
void swap(int *x, int *y){ 
  int t = *x; *x = *y; *y = t; 
}
int main(){
  int  a = 3,b=4;

  swap(&a,&b); /*a,b的地址传递给swap*/

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

在C++语言中,我们可以将swap的2个形式参数定义为“引用参数“,使得形式参数就是实际参数的别名,也就是引用参数和它引用的实际参数是同一块内存,这种参数传递方式叫做“传引用”。

/* 通过x,y对它们引用的内存进行修改,
   x就是a,y就是b,对x,y的修改就是对a,b的修改
*/
void swap(int &x, int &y){ 
  int t = x; x = y; y = t; 
}
int main(){
  int  a = 3,b=4;

  swap(a,b); 

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

引用变量的注意事项

  • 因为b是引用变量,所以在定义它时就必须指明它引用的是那个变量,而且一旦引用某个变量,就不能再引用其他变量

支付宝打赏 微信打赏

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