一、指针
1.1 指针初始化
//声明时初始化
int a=100;
int *p = &a;
//声明时不初始化,后期赋值
int a=100;
int *p = NULL;
p=&a;
//特别注意不能这样赋值,会发生错误,因此指针初始化最好加上NULL,减少直接赋值造成的危害
int *p;
*p = 100;
//正确的方式为
int k;
int *p = NULL;
p = &k;
*p = 100;
1.2 空类型指针
void *p;
void *p = NULL;
int *pl = NULL;
int a = 100;
pl = &a;
p = pl;
//转为int类型指针
cout<<*(int *)p<<endl;
//只能将对应类型的指针强转为对应的,不然会出现奇怪的异常
空类型指针可以接受任何类型的数据,可以将其转化为所对应的数据类型。
1.3 野指针
没有初始化的指针变量称为野指针,没有初始化的指针虽然可以使用,但有一定错误危害(不合法的内存空间),为了防止这种危害,良好的编程习惯是在定义指针变量时初始化为NULL,由于NULL处禁止写入,所以一旦有错误,就可以将错误的危害降到最小。
1.4 指针常量
指针的指向不能变,指向的值可以变;
int i = 9;
int *const p = &i;
*p = 3;
1.5 常量指针
指针的指向可以变,指向的值不能变;
int i=9;
int const *p = &i; //或者const int *p = &i;
//*p = 3;不能修改值
1.6 指向常量的指针常量
既不能修改指向,又不能修改指向的值
int i = 9;
int const *const p = &i; //或者 const int const *p = &i;
1.7 指针和一维数组
int a[10];
int *p;
p = &a[0];
将数组名赋值给整形变量
#include <iostream>
using namespace std;
int main()
{
int *p = NULL;
int a[10]={
0};
for(int i=0;i<10;i++)
{
a[i] = i;
}
p = a;
for(i=0;i<10;i++,p++)
{
cout<<*p<<endl;
}
return 0;
}
根据数组名获取数据
cout<<*(a+5)<<endl;//相当于是a[5]
1.7.1 指针操作数组说明
(p--)相当于a[i--],先对p进行 运算,再使p自减
(++p)相当于a[++i],先对p自加,再进行 运算
(--p)相当于a[--i],先对p自减,再进行 运算
p++相当于a[i++],先 运算,再进行自加
1.8 指针和二维数组
int a[4][3];
int *p=NULL;
p = a[0];//a[0]是二维数组中第一个元素的地址
//对于二维数组
a+n 代表第n行的首地址
&a[n] 代表第n行的首地址
&a[0][0]代表第0行的首地址,也代表第0行0列的首地址
a[0]+n 代表第0行第n个元素的地址
*(*(a+n)+m)代表第n行第m列元素
*(*(a[n]+m))代表第n行第m列元素
1.9 数组指针和指针数组
//定义一个数组指针,指向一个含有4个整型变量的数组
int (*b)[4];
//定义一个指针数组,存储指针的数组最多能存4个指针
int *c[4];
1.10 指针与字符数组
char *p;
//字符数组就是一个字符串,通过字符指针可以指向一个字符串
char *string="www.com";
//等价与下面的
char *string;
string = "www.com";
1.11 指针函数和函数指针
指针函数是返回值为指针的函数,函数指针是指函数类型的指针变量;
//指针函数,返回值为指针
int * swap(int a, int b);
//函数指针,指针类型的函数
int x=1,y=2;
int sum(int x,int y); //定义一个函数
int *a(int ,int ); //定义一个函数指针
a = sum; //让函数指针a指向函数sum
(*a)(x,y); //调用函数指针指向的函数
1.12 空指针调用函数
空指针可以指向任意类型函数或者将任意类型的函数指针赋值空类型指针。可以将空指针函数强转为其他函数
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int plus(int b)
{
return b+1;
}
int main()
{
void* pV = NULL; //定义一个空类型指针
int result = 0;
pV = plus; //让指针指向函数plus
cout<<"执行pV指向的函数:"<<endl;
result=((int(*)(int))pV)(10); //将空类型指针pV强制转换返回值整型、参数整型的函数指针
cout<<"result:"<<result<<endl;
return 0;
}
1.12.1 指针做返回值
返回的确实是地址,但是地址上的值被清空了,所以返回的指针指向的值不是函数体原来的值。
1.13 指针数组
指针数组的数组名也是一个指针,代表该指针数组的起始地址。
int *p[4];
int a=1;
p[0]=&a;
//获取指针数组元素指向的值
cout<<**p<<endl;
1.14 安全使用指针
可参考以下案例
#include <iostream>
using std::cout;
using std::endl;
int* newPointerGet(int* p1)
{
int k1 = 55;
p1 =new int; //变为堆内存。
* p1 = k1; //int型变量赋值操作
return p1;
}
int* PointerGet(int *p2)
{
int k2 = 55;
p2 =&k2; //指向函数中定义变量所在的栈内存,此段内存在函数执行后销毁
return p2;
}
int main()
{
cout<<"输出函数各自返回指针所指向的内存的值"<<endl;
int* p =NULL;
p = newPointerGet(p); //p具有堆内存的地址
int* i=NULL;
i = PointerGet(i); //i具有栈内存地址,内存内容被销毁
cout<<"newGet: "<<*p<<" , get: "<<*i<<endl;
cout<<"i所指向的内存没有被立刻销毁,执行一个输出语句后:"<<endl;
//i仍然为55,但不代表程序不会对它进行销毁
cout<<"newGet: "<<*p<<" , get: "<<*i<<endl; //执行其他的语句后,程序销毁了栈空间
delete p; //依照p销毁堆内存
cout<<"销毁堆内存后:"<<endl;
cout<<"*p: "<<*p<<endl;
return 0;
}
1.14.1 内存分配
栈区的缺点是无法改变占用的内存大小,堆区是一种动态分配的内存区域。
int *p = NULL;
p = new int; //申请动态分配内存
*p = 100;
cout<<*p<<endl;
1.14.2 堆内存释放
int *p = NULL;
p = new int;
*p = 100;
cout<<*p<<endl;
delete p; //释放堆内存
p =NULL; //这一步是防止使用已销毁的内存。这一句不能和上面一句颠倒顺序,不然导致内存泄露
内存泄露是指,某块有数据的堆内存没有指针指向它,这块内存犹如丢失一样,称为内存泄露。
//例如下面就是内存泄露的案例
int *p = NULL;
p = new int;
*p = 100;
int k;
p = &k;
//自此p的指向就变到了k,100这块内存没有指针指向它,导致这块内存泄露
注意指针只能访问内存中数据,不能访问寄存器中数据
从寄存器中读取数据比从内存中读取更快
拓展:
结构体中使用的是malloc分配空间 和free释放内存
#include<stdio.h>
#include <stdlib.h>
int main()
{
struct stu
{
int num;
char* name;
char sex;
float score;
}*ps;
ps = (struct stu*)malloc(sizeof(struct stu));
ps->num = 102;
ps->name = "狗蛋";
ps->sex = 'M';
ps->score = 62.5;
printf("Number=%d\nName=%s\n",ps->num,ps->name);
printf("Sex=%c\nScore=%.2f\n", ps->sex, ps->score);
free(ps);
return 0;
}
其他案例
使用递归和栈来计算公式
#include <stdlib.h>
#include <stdio.h>
double f1(int n, int x) /*自定义函数f1,递归的方法*/
{
if (n == 0)
return 1; /*n为0时返回值为1*/
else if (n == 1)
return 2 * x; /*n为1时返回值为2与x的乘积*/
else
return 2 * x * f1(n - 1, x) - 2 * (n - 1) * f1(n - 2, x);/*当n大于2时递归求值*/
}
double f2(int n, int x) /*自定义函数f2,栈的方法*/
{
struct STACK
{
int num; /*num用来存放n值*/
double data; /*data存放不同n所对应的不同结果*/
} stack[100];
int i, top = 0; /*变量数据类型为基本整型*/
double sum1 = 1, sum2; /*多项式的结果为双精度型*/
sum2 = 2 * x; /*当n是1的时候结果是2*/
for (i = n; i >= 2; i--)
{
top++; /*栈顶指针上移*/
stack[top].num = i; /*i进栈*/
}
while (top > 0)
{
/*求出栈顶元素对应的函数值*/
stack[top].data = 2 * x * sum2 - 2 * (stack[top].num - 1) * sum1;
sum1 = sum2; /*sum2赋给sum1*/
sum2 = stack[top].data; /*刚算出的函数值赋给sum2*/
top--; /*栈顶指针下移*/
}
return sum2; /*最终返回sum2的值*/
}
void main()
{
int x, n; /*定义x、n为基本整型*/
double sum1, sum2; /*sum1、sum2为双精度型*/
printf("请输入n:\n");
scanf("%d", &n); /*输入n值*/
printf("请输入x:\n");
scanf("%d", &x); /*输入x的值*/
sum1 = f1(n, x); /*调用f1,算出递归求多项式的值*/
sum2 = f2(n, x); /*调用f2,算出栈求多项式的值*/
printf("用递归算法得出的函数值是:%.2f\n", sum1); /*将递归方法算出的函数值输出*/
printf("用栈方法得出的函数值是:%.2f\n", sum2); /*将使用栈方法算出的函数值输出*/
}
1.15 引用
引用本质是指针常量 int * const
1.15.1 左值引用
c++11标准提出左值引用的概念,如果不加特殊声明,一般都是左值引用
引用实际是一个隐形指针,它为对象建立一个别名
int a =10;
int & b = a; //左值引用
b = 2;
cout<<a<<endl; //输出为2
(1) 一个引用被初始化后,无法使用它再去引用另一个对象。
(2) 引用变量只是其他对象的别名,对它的操作与对原来对象的操作具有相同作用
(3) 指针变量和引用的两点主要区别:
- 指针是一种数据结构,而引用不是数据结构,指针可以转换为它指向变量的数据结构,以便赋值运算符两边的类型相匹配;而使用引用时,系统要求引用和变量的数据类型必须相同,不能进行数据类型转换。
- 指针变量和引用变量都用来指向其他变量,但指针变量使用的语法更复杂;在定义了引用变量后,其使用方法与普通变量相同。
(4) 引用必须初始化
int a;
int b;
int &c;//编译无法通过
1.15.2 右值引用
类型 && i = 被引用的对象;
示例:
#include <iostream>
using namespace std;
int get()
{
int i=4;
return i;
}
int main()
{
int && k = get()+4;
//int & k = get() + 4; 出错
k++;
std::cout<<"k的值"<<k<<std::endl;
return 0;
}