衣带渐宽终不悔,为“指针”消得人憔悴(四)

至臻篇——

void 类型指针

void  =>  空类型
void*  =>  空类型指针,只存储地址的值,丢失类型,无法访问,要访问其值,
          我们必须对这个指针做出正确的类型转换,然后再间接引用指针。

所有其它类型的指针都可以隐式自动转换成 void 类型指针,反之需要强制转换

#include <stdio.h> 
#include <stdlib.h> 

int main(void){ 
	int arr[]={1, 2, 3, 4, 5}; 
	char ch = 'a'; 
	void *p = arr;//定义了一个void 类型的指针 
	
	//p++; //不可以, void * 指针不允许进行算术运算 
	
	p = &ch; //其它类型可以自动转换成void * 指针 
	
	//printf("数组第一个元素: %d\n", *p); //不可以进行访问 
	printf("p: 0x%p ch: 0x%p\n", p, &ch);
	
	//强制类型转化
	char * p1 = (char *)p; 
	printf("p1 指向的字符是: %c\n", *p1); 
	
	system("pause"); 
	return 0; 
}

    

函数指针
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

int compare_int(const void *a, const void *b){ 
	int *a1 = (int *) a; 
	int *b1 = (int *) b; 
	
	return *b1 - *a1; 
}

int compare_char(const void *a, const void *b){ 
	char c1 = *((char *) a); 
	char c2 = *((char *) b); 
	
	if(c1>='A' && c1<='Z') c1+=32; 
	if(c2>='A' && c2<='Z') c1+=32; 
	
	return c1 - c2; 
}

int main(void){ 
	int x = 10; 
	int y = 20; 
	//函数有没有地址? 
	//printf("compare_int 的地址: 0x%p \n", &compare_int); 
	//compare_int(&x, &y);
	
	//函数指针的定义 把函数声明移过来,把函数名改成 (* 函数指针名) 
	int (*fp)(const void *, const void *); 
	
	/*贝尔实验室的C和UNIX的开发者采用第1种形式,
	而伯克利的UNIX推广者却采用第2 种形式ANSI C 兼容了两种方式*/ 
	
	fp = &compare_int; 
	// (*fp)(&x, &y); //第1种,按普通指针解引的放式进行调用,(*fp) 等同compare_int 
	
	//fp(&x, &y); //第2种 直接调用 
	
	//qsort 对整形数组排序 
	int arr[]={2, 10, 30, 1, 11, 8, 7, 111, 520}; 
	qsort(arr, sizeof(arr)/sizeof(int), sizeof(int), &compare_int); 
	
	for(int i=0; i<sizeof(arr)/sizeof(int); i++){ 
		printf(" %d", arr[i]); 
	}
	
	//qsort 可以对任何类型的数组进行排序 
	char arr1[]={"abcdefghiABCDEFGHI"}; 
	qsort(arr1, sizeof(arr1)/sizeof(char)-1, sizeof(char), &compare_char);
	
	for(int i=0; i<sizeof(arr1)/sizeof(char)-1; i++){ 
		printf(" %c", arr1[i]); 
	}
	
	system("pause"); 
	return 0; 
}

    

特殊的“别名”:引用

变量名回顾
变量名实质上是一段连续存储空间的别名,是一个标号(门牌号)
程序中通过变量来申请并命名内存空间
通过变量的名字可以使用存储空间

问题 1:对一段连续的内存空间只能取一个别名吗?
    

1 引用概念
a) 在C++中新增加了引用的概念
b) 引用可以看作一个已定义变量的别名
c) 引用的语法:Type& name = var;
d) 引用做函数参数那?(引用作为函数参数声明时不进行初始化)

void main() { 
	int a = 10; //c编译器分配4个字节内存。。。a内存空间的别名 
	int &b = a; //b就是a的别名。。。 
	
	a =11;
	printf("a %d \n",a); //直接赋值 
	
	int *p = &a; 
	*p = 12; 
	printf("a %d \n",a); 
	
	b = 14; 
	printf("a:%d b:%d", a, b); 
	
	system("pause"); 
	return 0;
}

    
2 引用是 C++的概念
属于C++编译器对C的扩展

问题:C中可以编译通过吗?
结论:请不要用C的语法考虑 b=11

    
3 引用做函数参数

普通引用在声明时必须用其它的变量进行初始化,
引用作为函数参数声明时不进行初始化

//05复杂数据类型 的引用 
struct Teacher { 
	char name[64]; 
	int age ; 
}; 

void printfT(Teacher *pT) { 
	cout<<pT->age<<endl; 
}

//pT是t1的别名 ,相当于修改了t1 
void printfT2(Teacher &pT) { 
	//cout<<pT.age<<endl; 
	pT.age = 33; 
}

//pT和t1的是两个不同的变量 
void printfT3(Teacher pT) { 
	cout<<pT.age<<endl; 
	pT.age = 45; //只会修改pT变量 ,不会修改t1变量 
}

void main() { 
	Teacher t1; 
	t1.age = 35; 
	printfT(&t1); 
	
	printfT2(t1); //pT是t1的别名 
	printf("t1.age:%d \n", t1.age); //33
	
	printfT3(t1) ;// pT是形参 ,t1 copy一份数据 给pT //---> pT = t1 
	printf("t1.age:%d \n", t1.age); //35 
	
	cout<<"hello..."<<endl; 
	
	system("pause"); 
	return ; 
}

    
4 引用的意义
1)引用作为其它变量的别名而存在,因此在一些场合可以代替指针
2)引用相对于指针来说具有更好的可读性和实用性

int swap(int &a, int &b){ 
	int t = a; 
	a = b; 
	b = t; 
	
	return 0; 
}

int swap(int *a, int *b) { 
	int t = *a; 
	*a = *b; 
	*b = t; 
	
	return 0; 
}

    
5 引用的本质
1)引用在C++中的内部实现是一个常指针
        Type& name 等同于 Type* const name
2)C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的
   空间大 小与指针相同。
3)从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这是C++ 为了实用性而做出的细节隐藏
在这里插入图片描述

    
7 引用结论
1)当实参传给形参引用的时候,只不过是c++编译器帮我们程序员手工取了一个实参地 址,传给了形参引用(常量指针)

2)当我们使用引用语法的时,我们不去关心编译器引用是怎么做的 当我们分析奇怪的语法现象的时,我们才去考虑c++编译器是怎么做的

    

8 函数返回值是引用(引用当左值和右值)

C++引用使用时的难点:

当函数返回值为引用时 :
    若返回栈变量,不能成为其它引用的初始值,不能作为左值使用

若返回静态变量或全局变量 :
    可以成为其他引用的初始值
    即可作为右值使用,也可作为左值使用
(注:C++链式编程中,经常用到引用,运算符重载专题)

//返回值是基础类型,当引用

int getA1() { 
	int a; 
	a = 10; 
	return a; 
}

//基础类型a返回的时候,也会有一个副本 
int& getA2() { 
	int a; 
	a = 10; 
	return a; 
}

int* getA3() { 
	int a;
	a = 10; 
	return &a; 
}
//返回值是static变量,当引用

//static修饰变量的时候,变量不是一个临时变量 
int getA1() { 
	int a; 
	a = 10; 
	return a; 
}

int& getA2() { 
	static int a ; 
	a = 10; 
	return a; 
}

int& getAA2() { 
	int a; 
	a = 10; 
	return a; 
}

int* getA3() { 
	static int a; 
	a = 10; 
	return &a; 
}

int& getA4() { 
	static int a = 10; 
	return a; 
}

int main(void) { 
	int a1 = 1; 
	int a2 = 2;
	a1 = getA1(); 
	a2 = getA2(); 
	//int &a3 = a1; 
	//a2 = a3; 
	int &a3 = getA2(); 
	int *a4 = getA3(); 
	
	/*1.不管是指针还是引用,如果出现在右值里,我们要根据具体情况具体分析,
	如果是 局部变量,则会出现问题, 但是,编译器不会报错,后果自负 
	
	2.如果是静态变量或全局变量,则不会出现问题 */
	printf("a1: %d\n", a1); 
	printf("a2: %d\n", a2); 
	printf("a3: %d\n", a3); 
	printf("a4: %d\n", *a4); 
	getA4() = 100; 
	printf("getA4(): %d\n", getA4()); 
	
	system("pause"); 
	return 0;
}
//返回值是形参,当引用

int& g1(int &a) { 
	a = 99; 
	return a;
}

int& g2(int *p) { 
	*p = 100; 
	return *p; 
}

//当我们使用引用语法的时候 ,我们不去关心编译器引用是怎么做的 
//当我们分析乱码这种现象的时候,我们才去考虑c++编译器是怎么做的。。。。 

int main() { 
	int a1 = 10; 
	int &a5 = g1(a1); 
	
	printf("a1: %d a5: %d &a1:%p &a5:%p\n", a1, a5, &a1, &a5); 
	system("pause");
	return 0; 
}

    
返回值非基础类型

  1. 结构体相对简单,和普通类型一样
  2. 但如果返回值是类的对象,情况就变得复杂了,后面我们再进行讲解

    
9 指针引用


struct Teacher { 
	char name[64]; 
	int age; 
};

int getT1(struct Teacher ** p1) { 
	struct Teacher * tmp = (struct Teacher *)malloc(sizeof(struct Teacher)); 

	if (!tmp) { //!tmp 不等同于 tmp == NULL 
		return -1; 
	}
	
	*p1 = tmp; 
	tmp->age = 37; 
	return 0; 
}

int getT2(struct Teacher* &p2) { 
	struct Teacher * tmp = (struct Teacher *)malloc(sizeof(struct Teacher)); 

	if (!tmp) { //!tmp 不等同于 tmp == NULL 
		return -1; 
	}
	
	p2 = tmp;
	p2->age = 38; 
	return 0; 
}

int main(void) { 
	struct Teacher *p = NULL; 
	
	//int ret = getT1(&p); 
	int ret = getT2(p); 
	
	printf("p->age: %d\n", p->age); 
	
	system("pause"); 
	return 0; 
}

指针引用,其实底层的实现就是二级指针,只不过我们理解起来要比二级指针容易的多,我们不用计较引用底层的实现。“引用”就是一个外号!

例:
多啦A梦    外号    小叮当
那么小叮当就是哆啦A梦,小叮当没电了,就是哆啦A梦没电了;
哆啦A梦没电了,就是小叮当没电了,他们两个说的是同一个机器猫

    

常引用

在 C++中可以声明 const 引用
语法: const Type& name = var;
const 引用让变量拥有只读属性

分两种情况:

  1. 用变量初始化常引用
  2. 用字面量初始化常量引用
int main(void) { 
	int a = 10; 
	int &b = a; 
	printf("b: %d\n", b); 
	
	//1.用变量初始化常引用 
	int x = 20; 
	const int &y = x; //常引用是让变量引用变成只读,不能通过引用对变量进行修改 
	
	//2>用字面量初始化常量引用 
	//const int c1 = 10; 
	const int &c2 = 10;// 这样是否可行?
	//可行,这个是在 C++中,编译器会对这样的定义的引用 分配内存,
	//这算是一个特例 
	
	system("pause"); 
	return 0; 
}

Const 引用结论:

1)Const & int e 相当于 const int * const e
2)普通引用 相当于 int *const e1
3)当使用常量(字面量)对const引用进行初始化时,C++编译器会为常量值分配空间, 并将引用名作为这段空间的别名
4)使用字面量对const引用初始化后,将生成一个只读变量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值