C语言知识总结(3)

数组

数组的特点:

  • 只能存放一种类型的数据,比如int类型的数组、float类型的数组

  • 里面存放的数据称为“元素”

初始化方式

int a[3] = {10, 9, 6};

int a[3] = {10,9};

int a[] = {11, 7, 6};

int a[4] = {[1]=11,[0] = 7};

常见错误

int a[];

int[4] a;

int a[b];

a = {10, 11};

a[4] = {10,9,8,5}; 

内存分析

  • 数组存储空间的大小

  • 存储空间的划分(内存的分配是从高地址到低地址进行的,但一个数组内部元素又是从低到高进行的

  • 数组名的作用,查看元素地址
  • 数组越界的注意

二维数组

二维数组是一个特殊的一维数组:它的元素是一维数组。例如int a[2][3]可以看作由一维数组a[0]和一维数组a[1]组成,这两个一维数组都包含了3个int类型的元素

初始化

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

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

    int a[][5] = {3,21,31,2,32,1};

注意错误:

    int a[3][4];

    a[3] = {};

字符串

很多个字符组合在一起就是字符串了

初始化

  • char a[] = “123”;  和 char a [] = {‘1’,’2’,’3’};的区别,可以比较大小

  • “123”其实是由’1’、’2’、’3’、’’组成

  • “123”的存储分布

  • 字符串的输出”%s”,’’是不会输出的

的作用

''是一般字符串语句中的结束符号,可以判断字符串是否结束

字符串数组

初始化

char names[2][10] = { {'J','a','y',''}, {'J','i','m',''} };  
char names2[2][10] = { {"Jay"}, {"Jim"} };  
char names3[2][10] = { "Jay", "Jim" };

指针

定义格式    类名标识符 *指针变量名;    如  int *p;

先定义后赋值

// 简单取值
int a = 10;
int *p;
p = &a;
printf(“%d”, *p);
// 简单改值
*p = 9;

定义的同时赋值

int a = 10;
int *p = &a;

清空指针

p = 0;
p = NULL;

指针与数组

1、指向数组元素的指针与普通指针变量一样,例如

int a[5];
int *p=&a[0];
p=a; //数组名代表数组的首地址
p=&a[1];//指针指向第二个元素 

在数组里面,通过*(p+1)、*(p-1)就可以直接获取上下元素。
2、指针下标:在C语言中,中括号[]是下标运算符,一般我们习惯用数组名[下标]对数组元素进行访问,实际上,下标运算符可用于指针运算,例如:

int a[4];
int *p=&a[1];  //指针p指向数组的第二个元素

p[1] --> *(p+1) 下一个元素,即a[2]
p[-1] --> *(p-1) 上一个元素,即a[0]

下标运算符:D[N]没有规定D和N的顺序,N[D]也是可以的,例如:
1[p] --> *(1+p)
(p+1)[1] --> *(p+1+1) //用的人少
3、访问数组元素的4种方式:  a[i]、 *(a+i)、p[i]、*(p+i) 

#include <stdio.h>
int main(){
  int x[] = {1,2,3};
  int s=1,i,*p=x+2;  //*p = &x[2]
  for(i=2;i>=0;--i)
    s -= (-i)[p]; // (-i)[p] = *(-i+p) 1-1=0 0-2=-2 -2-3=-5
  printf("%d
",s); // -5
  return 0;  //1[p] --> *(1+p)
}

 指针与常量

1、指向常量的指针:指针变量指向一个常量,此时指针所指向的值不能修改,但指针本身(存储的地址)可以指向另外的对象,例如

int a = 3, b = 10;
const int *p = &a;
*p = 5;  //报错,不能修改
p = &b; //可以修改 

2、指针常量:声明指针常量,则指针本身不能改变。例如

int a=3, b=10;
int *const p = &a;
p = &b;  //报错,不能修改
*p = 5;  //可以修改

理解:看const后面是什么,如果是*p则表示指针的值不能修改,如果是p则表示指针不能更换指向。
3、类型转换:C语言中,如果将常量的地址赋值给非常量指针(类型转换),就可以修改地址里的值,从而影响代码中用到常量的表达式。例如

const int a=5;  //常量
int *p = (int*)&a;  //类型换换,去掉const
*p = 100;   //修改地址指向的值
int b = a+1;   //101 

指针与字符串 

1、字符串除了用字符数组处理外,还可以用字符指针来处理,字符指针指向字符串常量(连续存储区)的首地址。例如 

char str[10] = "Hello";
const char *pstr = "Hello";

注意:"Hello"字符串常量为 const char* ,所以字符指针定义也要用const char*。(C语言中也可以只用 char*)
2、赋值:可以将字符串整体赋值给字符指针,而字符数组除了初始化的时候是不能整体赋值的,如

char str[10];
const char *pstr;
str = "Hello";  //错误
pstr = "Hello"; //正确

理解:字符数组名是一个常量,不能被赋值,而字符指针是一个变量,存储字符串常量的地址,变量的值(存储的地址)是可以改变的。 
3、访问字符元素:访问字符串的字符元素,通过下标或指针移位的方式即可,和普通数组一样。如

const char *p = "Hello";
p[1];  //'e'
p[1] = 'A';  //报错,不能修改字符串常量的元素
char a[]="Hello", *pa=a;
pa[1] = 'A';  //可以修改 

注:要理解字符指针指向的是字符数组还是字符串常量。

数组指针

1、数组指针:指向一个数组的指针变量(注意不是指向数组的首元素)称为数组指针(也称行指针)。例如 

int a[4];
int *p1=a;  //指向数组首元素的指针,指向的类型是int
int (*p2)[4]=&a; //数组指针,指针指向的类型是4个int组成的数组
p1+1  --> a[1]
p2+1  --> a[5]   //注意这里+1是4个int的大小

注意:赋值要使用p2 = &a,如果用p2=a会报错,因为a的类型是 int *,  而&a的类型是int (*)[4],这是两种指针类型(虽然地址一样)。
2、访问数组元素:由于数组指针指向一个数组,那么*p就是数组本身,(*p)[i]表示数组的第i个元素,例如

int a[4]={10,20,30,40};
int (*p)[4]=&a;
(*p)[1] --> 20

数组指针与二维数组  

1、数组指针用于二维数组,指针指向以二维数组每一行数组,例如
int a[3][4];
//可看成3个int [4]的数组:a[0]、a[1]、a[2]
int (*p)[4]=a; //定义指向int [4]数组的指针变量
p --> a[0]
p+1 --> a[1]
注意:二维数组名a为int (*)[4]类型。
2、访问数组元素,如
p[0][1] --> a[0][1]
*(*(p+1)+2) --> a[1][2]
演示-用数组指针遍历输出二维数组{{1,2,3,4},{5,6,7},{8,9}}。
注意:p[i][j]  -- > *(*(p+i) + j)

#include <stdio.h>
int main(){
  int a[3][4]={{1,2,3,4},{5,6,7},{8,9}};
  int i,j,(*p)[4]=a;
  for(i=0;i<3;++i){
    for(j=0;j<4;++j)
     //printf("%d ",p[i][j]);
       printf("%d ",*(*(p+i)+j));
     printf("
");
  }
  return 0;
}

指针数组与二维数组

1、指针数组:指定义一个由指针组成的数组,即数组中的每个元素都是指针变量,例如

const char *p[4];  
//由p[0]..p[3]组成的数组,每个都是char*指针

2、指针数组用于二维数组,下标表示二维数组的行数,例如: 

int a[3][4];
//可看成3个int [4]的数组:a[0]、a[1]、a[2]
int *p[3];  //定义3个数组
p[0] = a[0];  //指针赋值
p[1] = a[1];

3、数组元素:由于p[i]的类型是int*数组,其元素可以用 +j ,或下标[j]来访问,如
p[0][1]  --> a[0][1]
*(p[0]+1) --> a[0][1]
演示-用指针数组遍历输出二维数组{{1,2,3,4},{5,6,7},{8,9}}。
注:p[i][j]  -- > *(p[i] + j)

#include <stdio.h>
int main(){
  int a[3][4]={{1,2,3,4},{5,6,7},{8,9}};
  int i, j, *p[3];
  p[0] = a[0];
  p[1] = a[1];
  p[2] = a[2];
  for(i=0;i<3;++i){
    for(j=0;j<4;++j)
      //printf("%d ",p[i][j]);
      printf("%d ",*(p[i]+j));
    printf("
");
  }
  return 0;
}

指针数组与字符串

指针数组适用于指向若干字符串(每个字符串不定长,用二维字符数组很浪费空间),会使字符串处理更加灵活,效率更高。例如
const char *p[3]={"Hello","new world","how are you"};
p[0][1] --> 'e'
*(p[0]+1) --> 'e'   
注:每一个字符串指针都是以''作为结尾。

指向指针的指针

1、指针变量*p存储的是一个地址,它自己也有地址,可以再用一个指针变量*prt指向p的地址,prt称之为指向指针的指针变量,简称二级指针,例如  int i, *p=&i, **prt=&p; 注意此时指针类型为“指向整型数据的指针变量”,不是整型数据。 prt = &i; //错误,指针类型不符
2、对指针数组*p[n]来说,数组名代表首地址,每一个元素都是指针型数据,因此它就是一个二级指针,也可以用一个二级指针进行赋值及运算,例如
int a[4]={1,3,5,7};
int *pa[4]={&a[0],&a[1],&a[2],&a[3]};
int **p=pa;
**p --> 1
**(p+1) --> 3

指针与结构体

1、结构体类型的指针变量,定义方式为
struct 结构体类型 *变量名;
struct student stu, *p;
p = &stu;
2、成员的引用:访问成员有以下三种方式:
stu.name;
(*p).name;
p->name; //指针专用,指向运算符,优先级比单目运算符高
p->age++; //先获得age成员,再++
练习-根据姓名查询学分
#include <stdio.h>
#include <string.h>
struct students{
    int no;
    char *name;
    int score;
};
struct students *find(struct students *s, int len, const char *name)
{
    while(strcmp(s->name , name) != 0) 
      s+=1;
    return s;
}
int main()
{
    struct students stu[] = {{14001, "张三", 60},
        {14002, "李四", 45},
        {14003, "张思", 81},
        {14004, "李武", 97},
        {14005, "张武", 75}};
    struct students *a = find(&stu[0], 5, "李武");
    printf("输出信息为,姓名:%s,学号:%d,成绩:%d", (*a).name, (*a).no, (*a).score);
    return 0;
}

指针作为函数参数

函数参数是指针变量时,传递的是指针存储的地址,而变量做参数时传递的是具体的值(会产生形参的拷贝)。指针做参数时,地址(指针自己的值)不能被修改,但地址指向的值可以被修改。例如

void change(int *p){
   *p = 20;
}
int main(){
   int a=5;
   change(&a);  //传入指针,在函数里a的值被修改
}

数组作为函数参数 

数组名可作为函数的实参和形参,传递的是数组的首地址,在函数被调用时,对形参指向的值的修改会使得实参数组发生变化。由于传递的是首地址,那么也可以用指针来表示形参或实参,效果相同。
1、形参实参都用数组名:

void f(int arr[],int len){...}
int main(){
  int a[10];
  f(a, 10);
}

2、实参用数组名,形参用指针变量

void f(int *x,int len){...}
int main(){
  int a[10];
  f(a, 10);
}

3、实参用指针变量,形参用数组名:

void f(int arr[],int len){...}
int main(){
  int a[10],*p=a;
  f(p, 10);
}

4、实参形参都用指针变量:

void f(int *x,int len){...}
int main(){
  int a[10],*p=a;
  f(p, 10);
}

演示-设计函数cat,将第二个参数的字符串被连接到第一个字符串后面。

#include <stdio.h>
void cat(char *p1, const char *p2){
  while(*p1) *p1++; //到p1的''位置,
  while(*p2)
    *p1++ = *p2++;  //将p2复制给p1的''后面位置
  *p1 = '';   //最后加个''
}
int main(){
  char s1[80]="Hello";  //长度要够
  const char *s2 = " world!";
  cat(s1,s2);
  printf("%s
",s1);
  return 0;
}

指针型函数

返回值类型为指针的函数称之为指针型函数,如
int *num(int x, int y);
类型 *函数名([形参列表]);
注意:返回指针类型意味着不需要生成拷贝,效率较高。
理解:*的优先级低于()的优先级,因此函数名先和后面的()结合,以上格式意味着首先是一个函数,然后返回值是一个指针。如果是以下格式:int (*num)(int x, int y)
则先将*和num结合,表示num是一个指针变量,指向一个函数(称为函数指针)。

函数指针

1、指向函数的指针变量称之为函数指针。例如
int (*func)(int x, int y);
类型 (*函数名)([形参列表]);
函数指针变量指向一个函数的地址,可以将同样原型的函数赋值给变量,例如

int max(int x,int y);
int (*p)(int x,int y);
p = max;  //将函数赋值给指针变量

定义了函数指针变量以后,就可以用此变量来调用函数,也可以将之作为函数的参数(回调函数)。
2、调用函数:用函数指针变量调用max和min函数。

int max(int x, int y);  //求最大值
int min(int x, int y);  //求最小值
int (*p)(int x, int y);
p=max;
printf("%d
",p(2,3));  //3
p=min;
printf("%d
",p(2,3));  //2

3、回调函数:又称callback,是一种事件驱动的编程模式,将函数指针变量作为参数传递给另外一个函数,函数里面当某些事件满足时会调用此函数指针变量指向的函数,例如。

void print(int n){  //回调函数,打印信息
  printf("回调函数调用:%d
",n);
}
void loop(int max, int (*p)(int n)){ 
//遍历1..n,如果7的倍数就打印信息
  int i;
  for(i=0;i<max;++i){
     if(i%7==0) p(i);
  }
}

理解:诸葛亮交给赵云三个锦囊,如果发生**事情,就打开第几个锦囊,这里的锦囊就是传递进去的函数指针变量。
4、自定义函数指针类型:可以使用typedef来自定义一种函数指针类型,例如

typedef (*MYFUNC)(int n); //自定义类型PRINT_FUNC
MYFUNC p = print;  //将print函数赋值给变量p
p(3);  //调用函数指针变量

动态分配内存

1、用malloc函数可以在程序中动态分配内存空间,返回void*指针,这时需要用一个指针变量将首地址保存起来,以后可以使用,如果没有保存,则这块空间就丢失了(称之为内存泄漏),例如

int *p = (int*)malloc(sizeof(int)); 
*p = 5;  
p = (int*)malloc(sizeof(int));
//p指向新地址,原内存丢失(泄漏)

注意:需要#include <malloc.h>。
2、动态数组:可以用malloc函数来定义动态数组。例如

int *p = (int*)malloc(sizeof(int)*10); 
//分配10个元素的数组
p[0] = 1; 

3、释放内存:free函数用来将动态分配的内存空间还给系统,之后系统可以继续使用回收的内存。例如

int *p = (int*)malloc(sizeof(int));
free(p);  
p = NULL;
int *p2 = (int*)malloc(sizeof(int)*5);
free(p2); 
p2 = NULL;

注意:指针变量被释放以后,不能再赋值,因为它没有内存空间,申请内存后才能赋值或运算。为了防止继续赋值导致崩溃,建议将指针设置为NULL。
4、栈和堆:malloc分配的内存空间从“堆”上申请内存,从低到高,而自动分配的如int a,是在“栈”上申请的内存空间,从高到低。

原文地址:https://www.cnblogs.com/melodyzhy/p/4623281.html