C/C++基础整理(二)

接着上一篇C/C++整理的第二部分,本篇主要涉及到的是函数、指针部分

本篇推荐阅读博客:

深入理解char * ,char ** ,char a[] ,char *a[]的区别

让你不再害怕指针——C指针详解(经典,非常详细)

对于递归有没有什么好的理解方法?

函数

定义函数

返回类型 函数名 ( 形式参数 ){
	声明
	语句
}

细节问题:

  • 函数无法返回数组。

  • 指定返回类型是void型说明函数没有返回值。

  • C89中,如果忽略返回类型,那么会假定函数返回值的类型是int

  • C99中,忽略返回类型是非法的。

全局变量、局部变量、值传递的传参方式

全局变量是指在定义之后的所有程序段内部都有效的变量(定义在所有函数之前)

局部变量定义在函数内部,且只在函数内部生效,函数结束时局部变量销毁

值传递

#include<stdio.h>

void change(int x){
    x = x + 1;
}

int main(void){
    int x = 10;
    change(x);
    printf("%d",x);		//	10
    return 0;
}

从结果分析:

当在主函数中定义了x以后,将其作为change()函数的参数传入,并令x加1,但是最后输出时x却仍然是10,这是因为change()函数的参数x为局部变量,仅在函数内部生效,通过change(x)传递进去的x其实只是传进去的一个副本,也即change函数的参数xmain函数里的x其实是作用于两个不同函数的不同变量(虽然名字相同),取成不同的名字也是可以的。这种传递参数的方式称为值传递

数组作为函数参数

函数的参数也可以是数组,且数组作为参数时,参数中数组的第一维不需要填写长度(如果是二维数组,那么第二维需要填写长度

数组作为参数时,在函数的调用中对数组元素的修改等同于是对原数组元素的修改(这与普通的局部变量不同,具体原因:数组传参时实际上传递的是数组第一个元素的地址,指针部分详谈

注意:虽然数组可以作为参数,但是却不允许作为返回类型出现,如果想要返回数组,只能将返回的数组作为参数传入

函数递归调用

递归就是函数自己调自己的过程初学递归,对于递归一直难于理解,这里推荐的一篇文章

对于递归有没有什么好的理解方法?

指针

在计算机内存中,每个字节(理解为房间)都会有一个地址(可理解为房间号),即变量的存储位置,计算机就是通过地址找到某个变量的,变量的地址一般指它占用的字节中第一个字节存放的地址,一个int型的变量的地址就是它占用的4Byte当中第一个字节的地址

指针就是变量的地址,指针变量:用于存放指针

指针在C语言中是较为难理解的一部分,这里只是做一些基础总结,推荐两篇博客

一个房间号“指向”一个房间,对应到计算机上就是一个地址“指向”一个变量,在C语言中用“指针”来表示内存地址,可以理解为:指针就是变量的地址(指针是一个unsigned类型的整数) 获取变量地址就是通过取地址运算符&,只要在变量前面加上&,就表示变量的地址

#include<stdio.h>

int main(void){
    int a = 5;
    int *p1 = &a;
    double pi = 3.14;
    double *p2 = &pi;
    printf("%d\n",sizeof(p1));		//	4
    printf("%d\n",sizeof(p2));		//	4
    return 0;
}

从输出的结果我们可以看出:指针是一个unsigned类型的整数,并不会因为指针指向的是double(8Byte),对应指针变量分配空间变成8Byte

定义、初始化指针变量

基本类型 *指针变量名
int *p1;		//	在使用指针变量 p 之前对其进行初始化是至关重要的
int a;
int *p2 = &a; 	// 一般是把变量的地址取出来,然后赋给对应类型的指针变量
  • 指针变量前面必须有“ ***** ,表示该变量的类型为指针型变量。

  • 定义指针变量时必须指定基本类型,基类型表明了该指针变量所指向的变量类型。

  • int * 是指针变量的类型,而后面的p才是变量名,用来存储地址,&a是赋值给p而不是*p,也就是星号是类型的一部分

  • 间接寻址运算符 * 访问到指针所指的对象,可以把 * 理解为一把开启房间的钥匙,将其放在p的前面,这样*p就可以把房间打开,然后获取变量a的值

    int a =5;
    int *p = &a;
    printf("%d\n", *p);		// 5
    
    //	对变量使用&运算符产生指向变量的指针,而对指针使用*运算符则可以返回到原始变量
    j = *&i;   				/* same as j = i; */
    //	只要p指向i,*p就是i的别名,*p拥有和i相同的值,改变*p的值,同时也会改变i的值
    
  • 将间接寻址运算符应用于未初始化的指针变量,会导致未定义的行为,对 *p 进行赋值则更加危险

    int *p;
    printf("%d", *p);   /*** WRONG ***/
    
    int *p;
    *p = 1;   			/*** WRONG ***/
    
  • 指针变量的值可以是普通变量的地址,也可以是数组的地址数组元素的地址

    image-20200212210331685
  • 任意数量的指针变量都可以指向同一个对象

指针赋值与运算

指针赋值

p = q;			//	指针赋值语句	结果:指针q与p指向相同的位置
*p = *q;		//	不会指向同一变量,下图所示
image-20200213194210095

指针加减法 ==> 常用于数组中

对于一个int * 类型的变量p来说,p + 1 是指 p 所指的int型变量的下一个int型变量地址,这里所谓的**“下一个”是跨越了一整个int型(sizeof(int)个Byte ==> 4Byte)**

指针变量作为函数参数

指针类型可以作为函数参数的类型,**这时视为把变量的地址传入函数,如果在函数中对这个地址中的元素进行改变,原先的数据就会确实地被改变 ==> 地址传递 **

用一个例子来理解: 使用指针交换两个数

#include<stdio.h>

void swap(int *p1, int *p2){
	int temp;
	temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

int main(void){
    int a = 1, b = 2;
    int *p1 = &a, *p2 = &b;
    swap(p1, p2);       // only 指针名
    printf("a = %d, b = %d", *p1, *p2);		// a = 2, b = 1
    return 0;
}

上述代码中,把&aa的地址)和&bb的地址)作为参数传入,使得swap函数中int*类型指针变量a存放&a、指针变量b存放&b。这时,swap函数中的ab都是地址,而 *a*b就是地址中存放的数据,可以“看成”是int型变量,接下来就进行正常交换

我们再来看几个错误交换的例子

// 值传递并不能对数据产生实质影响
void swap1(int p1, int p2){
    int temp = p1;
    p1 = p2;
    p2 = temp;
}

// 很多人认为*temp、*p1、*p2都可以“看作”int型变量,可这样交换,但这里的错误是因为temp没有初始化,存放的地址是随机的
void swap2(int *p1, int *p2){
    int *temp;		
    *temp = *p1;
    *p1 = *p2;
    *p2 = *temp;
}

// 这里的错误是因为直接把这个地址进行交换,我们之前说过地址其实是一个“无符号整型”的数,其本身和普通变量一样只是“值传递”,对地址本身进行修改其实跟之前对传入的普通变量进行交换的函数是一样的作用,都只是副本,没法对数据产生实质性影响 ==> 解决方法:指针的引用
void swap3(int *p1, int *p2){
    int *temp = p1;
    p1 = p2;
    p2 = temp;
}

const保护参数

当传递指向变量x的指针给一个函数f时,通常意味着x的值将被修改f(&x);然而有的时候,f 可能仅仅需要对x的值进行检查,而不需要对其进行修改。可以使用const关键字确保函数不会修改指针参数所指向的对象

// 如果函数中的语句试图修改 *p 的值,编译器能够捕获此类错误。
void f(const int *p){
	*p = 0;   /*** WRONG ***/
}

指针作为返回值

int *max(int *a, int *b){
	if (*a > *b)
	   return a;
	else
	   return b;
}

int main(void){
  	int *p, i, j;
	...
	p = max(&i, &j);
}

注意:不要返回指向自动局部变量的指针,一旦函数返回,局部变量将不复存在

指针与数组

在C语言中,数组名称也作为数组的首地址使用

*a = 7;  		 	 /* stores 7 in a[0] */
*(a + 1) = 12;  	 /* stores 12 in a[1] */

// 数组遍历
for (p = &a[0]; p < &a[N]; p++)
	sum += *p;

// 可以简化为如下形式:
for (p = a; p < a + N; p++)
	sum += *p;

// 尽管数组名可以用作指针,但不能对其赋值,试图使其指向其它位置会导致错误发生:
while (*a != 0)
  	a++;           /*** WRONG ***/

通常情况下,a + i &a[i]是等同的,均表示指向数组 a 中元素i的指针
同样地, *(a+i)a[i]也是等同的,均表示数组 a 中的元素 i自身

如果指针p指向数组a的元素,则可以通过对指针p进行指针算术运算(或地址算术运算)访问数组a中其他元素:C语言支持三种类型的指针算术运算(且仅有这三种类型)

  • 指针加上整数

    如果 p 指向数组元素 a[i],则p + j指向数组元素 a[i + j]

    image-20200213194350262
  • 指针减去整数

    如果p 指向 a[i],则p - j 指向 a[i - j]

    image-20200213194420453
  • 两个指针相减 ==> 针对指向同一数组

    当两个指针相减时,结果为指针之间的距离(以数组元素的个数作为度量)。
    如果p指向a[i]q指向a[j],则 p – q等于i – j

    image-20200213194440774

指针引用(C++特性)

引用:不使用指针、不产生副本达到修改传入参数的目的 ==> 对引用变量的操作就是对原变量的操作

注意:要把引用的&和取地址符&区分开,引用并不是取地址的意思

#include<stdio.h>

int main(void){
    int x = 2;
    change(x);
    printf("%d\n",x);		//	1
    return 0;
}

void change(int &x){
    x = 1;
}

我们在上面写过一个通过将传递至交换来达到交换两个变量的效果,但是失败了=>[swap3()](# 指针变量作为函数参数),这是因为对指针变量本身的修改无法作用到原指针变量上,那么就可以使用指针的引用来实现这个效果

#include<stdio.h>

int main(void){
    int a = 1, b = 2;
    int *p1 = &a, *p2 = &b;
    swap(p1, p2);
//	swap(&a, &b);		/*** WRONG 由于引用是产生变量的别名,因此常量不可使用 ***/		
    printf("a = %d, b = %d\n",*p1, *p2);	// a = 2, b = 1
    return 0;
}

void swap(int* &p1, int* &p2){
    int* temp = p1;
    p1 = p2;
    p2 = temp;
}
赞赏