C语言知识总结(3)

数组

数组的特点:

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

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

初始化方式

int a[] = {, , };

int a[] = {,};

int a[] = {, , };

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

常见错误

int a[];

int[] a;

int a[b];

a = {, };

a[] = {,,,}; 

内存分析

  • 数组存储空间的大小

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

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

二维数组

二维数组是一个特殊的一维数组:它的元素是一维数组。例如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’、’\0’组成

  • “123”的存储分布

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

\0的作用

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

字符串数组

初始化

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

指针

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

先定义后赋值

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

定义的同时赋值

int a = ;
int *p = &a;

清空指针

p = ;
p = NULL;

指针与数组

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

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

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

int a[];
int *p=&a[]; //指针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[] = {,,};
int s=,i,*p=x+; //*p = &x[2]
for(i=;i>=;--i)
s -= (-i)[p]; // (-i)[p] = *(-i+p) 1-1=0 0-2=-2 -2-3=-5
printf("%d\n",s); // -5
return ; //1[p] --> *(1+p)
}

 指针与常量

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

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

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

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

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

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

指针与字符串 

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

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

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

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

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

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

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

数组指针

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

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

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

int a[]={,,,};
int (*p)[]=&a;
(*p)[] -->

数组指针与二维数组

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[][]={{,,,},{,,},{,}};
int i,j,(*p)[]=a;
for(i=;i<;++i){
for(j=;j<;++j)
//printf("%d ",p[i][j]);
printf("%d ",*(*(p+i)+j));
printf("\n");
}
return ;
}

指针数组与二维数组

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

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

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

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

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[][]={{,,,},{,,},{,}};
int i, j, *p[];
p[] = a[];
p[] = a[];
p[] = a[];
for(i=;i<;++i){
for(j=;j<;++j)
//printf("%d ",p[i][j]);
printf("%d ",*(p[i]+j));
printf("\n");
}
return ;
}

指针数组与字符串

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

指向指针的指针

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) != )
s+=;
return s;
}
int main()
{
struct students stu[] = {{, "张三", },
{, "李四", },
{, "张思", },
{, "李武", },
{, "张武", }};
struct students *a = find(&stu[], , "李武");
printf("输出信息为,姓名:%s,学号:%d,成绩:%d", (*a).name, (*a).no, (*a).score);
return ;
}

指针作为函数参数

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

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

数组作为函数参数 

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

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

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

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

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

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

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

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

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

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

指针型函数

返回值类型为指针的函数称之为指针型函数,如
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\n",p(,)); //
p=min;
printf("%d\n",p(,)); //

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

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

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

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

动态分配内存

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

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

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

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

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

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

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

上一篇:(十九)git版本管理软件——搭建git服务器


下一篇:git学习(十) idea git reset 操作