至臻篇——
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;
}
返回值非基础类型
- 结构体相对简单,和普通类型一样
- 但如果返回值是类的对象,情况就变得复杂了,后面我们再进行讲解
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 引用让变量拥有只读属性
分两种情况:
- 用变量初始化常引用
- 用字面量初始化常量引用
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引用初始化后,将生成一个只读变量