c++指针最全总结(附源码和详细总结)

目录

哈喽,我又来啦?

指针是什么?What is a pointer?

简单点说

一级指针的定义

指针的赋值

指针的输出

二级指针的定义

简单说

二级指针的赋值

二级指针的输出

改变一级指针指向

二级指针的步长

改变n-1级指针的指向

当指针遇上函数重载

程序

new的用法

指针和数组

普通方法遍历列表

用指针遍历列表

用指针访问数组元素

指针数组

结构体指针

定义结构体

一般结构体变量的访问方式

求指针的内存

做点练习题

选择题

编程题

1.考试成绩

考试成绩(2)

通过指针间接排序

通过指针间接排序(2)

馅饼吃货可知否

最后


哈喽,我又来啦?

hello?

我是「 YR_T

众所周知

c++相对于其它语言的

优势就在与c++有「 指针 」

可能你之前有学习过指针

可是

你真的把指针彻底搞懂了吗?

如果还没有,那再来复习一下?

 

这篇文章但凡是和指针沾点边的,都加进来了

指针是什么?What is a pointer?

指针是变量在内存中的地址(A pointer is the memory address of a variable)

这个很重要

首先,来看一下内存空间(图片来源于网络)

简单点说

比如你有一个int类型的变量,里面的内容是10

把这个变量的地址保存在指针里,注意了指针里面保存的不是这个变量本身,而是变量在内存中的地址

比如你要买一本书,你去了图书馆,查到了这本书在0x6ffe04位置上

你拿到了这个位置,并不代表你拿到了这本书,你要通过这个位置来找到这本书

这下懂了吧

一级指针的定义

int *p;//语法:类型 * 指针变量名;

指针的赋值

由于指针里存的是变量的地址,看这个程序


int *p;//定义一个指针
p=&a;//&是取址符

//或者
int *p=&a;

指针的输出

我们写这样一个程序

#include<iostream>
using namespace std;
int main()
{
	int a=10;
	int *p=&a;
	cout<<"指针p:"<<*p<<endl<<"a的地址:"<<&a<<endl<<"a:"<<a;
	return 0;
}

运行结果:

指针p:10
a的地址:0x6ffe04
a:10
--------------------------------
Process exited after 0.04383 seconds with return value 0
请按任意键继续. . .

可以看到,我们如果要输出a

不管是通过指针输出还是直接输出变量a结果是一样

二级指针的定义

看这张图

简单说

简单说,二级指针就是指针的指针,二级指针里存放的是指针的地址

二级指针的赋值

二级指针的赋值和一级指针一样

int a=10;
int *pi=&a;
int **pi=&pi;

二级指针的输出

我们写这样一个程序

#include<iostream>
using namespace std;
int main()
{
	int a=10;
	int *p=&a;
	int **pp=&p;
	cout<<"*p:"<<*p<<endl<<"&a:"<<&a<<endl<<"a:"<<a<<endl<<"**pp:"<<**pp<<endl<<"&pp:"<<&pp<<endl<<"&p:"<<&p;
	return 0;
}

运行结果:

*p:10
&a:0x6ffdfc
a:10
**pp:10
&pp:0x6ffde8
&p:0x6ffdf0
--------------------------------
Process exited after 0.06748 seconds with return value 0
请按任意键继续. . .

由此可见,二级指针和一级指针的输出是一样的

改变一级指针指向

#include <iostream>
#include <stdlib.h>
 
using namespace std;
 
int main()
{
	int i = 30;
 
	int *pi = &i;
	std::cout << "一级指针*pi = " << *pi << std::endl;       //一级指针
 
	int **ppi = &pi;
	std::cout << "二级指针**ppi = " << **ppi << std::endl;   //二级指针
 
	*pi = 20;
	std::cout << "改变一级指针内容: *pi = " << *pi << std::endl;  //改变一级指针值
	std::cout << "一级指针*pi = " << *pi << std::endl;       //二级指针
 
	int b = 10;
	*ppi = &b;
	std::cout << "改变一级指针指向*pi = " << *pi << std::endl;   //改变一级指针的指向
	std::cout << "二级指针**ppi = " << **ppi << std::endl;   
 
	system("pause");
	return 0;
}

运行结果

一级指针*pi = 30
二级指针**ppi = 30
改变一级指针内容: *pi = 20
一级指针*pi = 20
改变一级指针指向*pi = 10
二级指针**ppi = 10
请按任意键继续. . .

--------------------------------
Process exited after 3.002 seconds with return value 0
请按任意键继续. . .

二级指针的步长

所有类型的二级指针,由于均指向一级指针类型,一级指针类型大小是 4,所以二级指针的步长也是 4,这个信息很重要

改变n-1级指针的指向

  • 可以通过一级指针,修改 0  级指针(变量)的内容。
  • 可以通过二级指针,修改一级指针的指向。
  • 可以通过三级指针,修改二级指针的指向。
  •  ·····
  • 可以通过 n  级指针,修改 n-1 

当指针遇上函数重载

C++编译器会按照函数指针的类型自动选择重载函数

程序

看这个程序

#include <iostream>
 
using namespace std;
 
void print(int a)
{
    cout << "a is " << a << endl;
}
void print()
{
    cout << "hello world" << endl;
}
 
typedef void (* Fun)(int);
typedef void (* Fun2)();
 
int main()
{
    Fun pPrint = print; 
    Fun2 pPrint2 = print;
    pPrint(12);
    pPrint2();
    return 0;
}

运行结果:

a is 12
hello world

重载函数作为参数传递时,特别形参的类型不是确定的函数指针类型时,如void *,例如Qt中的QObject::connect()函数,重载的信号或槽传入到connect时,可以使用static_cast<>来区别重载函数:

connect(subWidget, static_cast<void(SubWidget::*)()>(&SubWidget::switchWin), this, &MainWidget::switchWinSlot);

connect()函数的第2个和第4个形参都是const char *类型,不是指定好的函数指针类型,所以我们可以通过static<>来区分重载函数的版本。

new的用法

new其实就是告诉计算机开辟一段新的空间,但是和一般的声明不同的是,new开辟的空间在堆上,而一般声明的变量存放在栈上。通常来说,当在局部函数中new出一段新的空间,该段空间在局部函数调用结束后仍然能够使用,可以用来向主函数传递参数。另外需要注意的是,new的使用格式,new出来的是一段空间的首地址。所以一般需要用指针来存放这段地址

练练英语:

New tells the computer to create a new space, but unlike the normal declaration, the space created by new is on the heap, while the normal declaration variables are stored on the stack. In general, when a new segment of space is created in a local function, that segment of space can be used after the local function call ends and can be used to pass arguments to the main function. Another thing to note is the format of the new, which is the first address of a space. So you usually have to use a pointer to store this address.

具体的代码如下:
 

#include <iostream>
using namespace std;
 
int example1()
{
  //可以在new后面直接赋值
  int *p = new int(3);
  //也可以单独赋值
  //*p = 3;
 
  //如果不想使用指针,可以定义一个变量,在new之前用“*”表示new出来的内容
  int q = *new int;
  q = 1;
  cout << q << endl;
 
  return *p;
}
 
int* example2()
{
  //当new一个数组时,同样用一个指针接住数组的首地址
  int *q = new int[3];
  for(int i=0; i<3; i++)
    q[i] = i;
 
  return q;
}
 
struct student
{
  string name;
  int score;
};
 
 
student* example3()
{
  //这里是用一个结构体指针接住结构体数组的首地址
  //对于结构体指针,个人认为目前这种赋值方法比较方便
  student *stlist = new student[3]{{"abc", 90}, {"bac", 78}, {"ccd", 93}};
 
  return stlist;
}
 
 
 
int main()
{
  int e1 = example1();
  cout <<"e1: "<< e1 << endl;
 
  int *e2 = example2();
  for(int i=0; i<3; i++)
    cout << e2[i] << " ";
  cout << endl;
 
 
  student *st1 = example3();
 
  for(int i=0; i<3; i++)
    cout << st1[i].name << " " << st1[i].score << endl;
 
 
 
  return 0;
}

运行结果

1
e1: 3
0 1 2
abc 90
bac 78
ccd 93

--------------------------------
Process exited after 0.06289 seconds with return value 0
请按任意键继续. . .

指针和数组

普通方法遍历列表

int i, a[] = {3,4,5,6,7,3,7,4,4,6};
for (i = 0; i <= 9; i++)
{
    std::cout << a[i] std::endl;
}

用指针遍历列表

int i, a[] = {3,4,5,6,7,3,7,4,4,6};
for (i = 0; i <= 9; i++)
{
std::cout << *(a+i) << std<<endl;;
}

这两种的效果是一毛一样

用指针访问数组元素

int i, *pa, a[] = {3,4,5,6,7,3,7,4,4,6};
pa = a; 
for (i = 0; i <= 9; i++)

{
 std::cout <<  pa[i] << std::endl;
}

指针数组

指针数组的本质是数组,数组中每一个成员是一个指针。定义形式如下:
char * pArray[10];
语法解析:pArray 先与“[ ]”结合,构成一个数组的定义,char *修饰的是数组的内容,即数组的每个元素。

#include <iostream>
#include <stdlib.h>
 
using namespace std;
 
int main()
{
	char * pArray[] ={"apple","pear","banana","orange","pineApple"};
	for(int i=0; i<sizeof(pArray)/sizeof(*pArray); i++)
	{
		std::cout << pArray[i] << std::endl;
	} 
 
	system("pause");
	return 0;
}

运行结果

apple
pear
banana
orange
pineApple
请按任意键继续. . .

--------------------------------
Process exited after 1.655 seconds with return value 0
请按任意键继续. . .

结构体指针

c++结构体指针,顾名思义就是指向结构体的一个指针

定义结构体

struct My{
    My *left;
    My *right;
    int val;
    My(){}
    My(int val):left(NULL),right(NULL),val(val){}
};

一般结构体变量的访问方式

void test1(){
    My m;
    m.val = 1;
    cout<<m.val<<endl;
}

可见,结构体中的变量,可以直接通过点操作符来访问。

而对于结构体指针而言:必须通过->符号来访问指针所指结构体的变量。

void test2(){
    My m;
    m.val = 1;
    My *mm;
    mm = &m;
    cout<<mm->val<<endl;
}

声明一个结构体指针记得初始化,记得初始化,记得初始化

void test3(){
    My *m;
    m = new My(3);
    m->val = 4;
    cout<<m->val<<endl;
}

这就是new的应用,用法上面已将讲过了

求指针的内存

cout << sizeof(pn) << endl;//4
cout << sizeof(&n[0]) << endl;//4

做点练习题

学了这么多,不如做点题吧

 对了,前面的选择题不是只有指针的,编程题是关于指针

选择题

看答案和解析的方法如下

if(你是PC端){
    鼠标选中蓝色区域看答案和解析();
}
else if(你是APP端){
    复制蓝色区域里的内容随便粘贴到一个地方看答案和解析();
}
else{
    cout<<"还有其它的吗?!?!?!?";
}

面向对象方法中,不属于“对象”基本特点的是(   A      )。(3分)

A) 一致性
B) 分类性
C) 多态性
D) 标识唯一性

[解析] 本题考查的是对象的基本特点。对象具有标识唯一性、分类性、多态性、封装性和模块独立性好这5个基本特点,所以本题应该选择A。

对类的构造函数和析构函数描述正确的是(A)。(3分)
  A. 构造函数可以重载,析构函数不能重载
  B. 构造函数不能重载,析构函数可以重载
  C. 构造函数可以重载,析构函数也可以重载
  D. 构造函数不能重载,析构函数也不能重裁

[解析]一个类中只能定义一个析构函数,否则会造成对同…对象的多次删除;而构造函数可以根据不同的参数个数和类型进行多次重载。

C++系统预定义了4个用于标准数据流的对象,下列选项中不属于此类对象的是(D)。(3分)
          A. cout
          B. cin
          C. cerr
          D. cset

[解析]cin:标准输入 cout:标准输出 cerr:标准错误输出,无缓冲区 clog:同上,标准错误流,有缓冲区

下列情况中,不会调用拷贝构造函数的是(B)。(3分)
        A. 用一个对象去初始化同一类的另一个新对象时
        B. 将类的一个对象赋值给该类的另一个对象时
        C. 函数的形参是类的对象,调用函数进行形参和实参结合时
        D. 函数的返回值是类的对象,函数执行返回调用时
[解析]一般用重载=运算符

有如下类定义:

class Foo
{
public:
          Foo(int v):value(V){  }     //①
          ~Foo(){}                            //②
private:
          Foo(){}                               //③
          int value=0;                      //④
};

其中存在语法错误的行是(  D   )。(3分)
          A. ①
          B. ②
          C. ③ 
          D. ④
[解析]不能直接在类中初始化
 

怎么样,题出的还不错吧?

编程题

1.考试成绩

请编写一个程序,动态分配一个足够大的数组来保存用户定义的考试成绩。一旦输入了所有的分数,数组就应该被传递给一个按照升序排序的函数。应该调用另一个函数来计平均分数。程序应该显示已排序的分数列表和平均分,并加上恰当的标题。请尽可能使用指针表示法而不是数组表示法。
输入验证:考试成绩不接受负数。
 

#include <iostream>

using namespace std;

void sort(double *s,int n)
{
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n-i-1;j++)
        {
            if(*(s+j+1)>*(s+j))
            {
                double tmp=*(s+j+1);
                *(s+j+1)=*(s+j);
                *(s+j)=tmp;
            }
        }
    }
}

double adv(double *s,int n)
{
    double sum=0.0;
    for(int i=0;i<n;i++)
        sum+=*(s+i);
    return sum/n;
}

void print(double *s,int n,double advscore)
{
    cout<<'t'<<"考试成绩表"<<endl;
    cout<<"------------------------"<<endl;
    for(int i=0;i<n;i++)
    {
        cout<<i+1<<'t'<<*(s+i)<<endl;
    }
    cout<<"平均成绩为:"<<advscore;
}
int main()
{
    double *score=new double[1000];
    cout<<"请输入成绩(输入101结束):"<<endl;
    int i=0;//计数
    while(1)
    {
        cin>>*(score+i);
        if(*(score+i)<0)
        {
            cout<<"输入非法!请重新输入:"<<endl;
            cin>>*(score+i);
        }
        else if(*(score+i)==9999)
             break;
        i++;
    }
    sort(score,i); //排序
    double advscore=adv(score,i);//计算平均分
    print(score,i,advscore);//输出
    return 0;
}

考试成绩(2)

修改编程题的程序以允许用户输入名称-分数对。对于每个参加考试的学生,用户先输入代表学生姓名的字符串,然后输入代表学生成绩的整数。修改排序和平均分计算函数,以便它们采用结构数组,每个结构包含单个学生的名称和分数。在遍历数组时,使用指针而不是数组索引。

#include <iostream>
#include <string>
using namespace std;
struct student
{
    string name;//名字
    int score;//分数
};

void sort(student *s,int n)
{
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n-i-1;j++)
        {
            if(s[j+1].score>s[j].score)
            {
                student tmp=s[j+1];
                s[j+1]=s[j];
                s[j]=tmp;
            }
        }
    }
}

double adv(student *s,int n)
{
    int sum=0;
    for(int i=0;i<n;i++)
        sum+=s[i].score;
    return sum/n;
}

void print(student *s,int n,double advscore)
{
    cout<<'t'<<"考试成绩表"<<endl;
    cout<<"------------------------"<<endl;
    for(int i=0;i<n;i++)
    {
        cout<<i+1<<'t'<<s[i].name<<'t'<<s[i].score<<endl;
    }
    cout<<"平均成绩为:"<<advscore;
}

int main()
{
    student *p=new student[1000];
    cout<<"请输入姓名和成绩(输入#结束):"<<endl;
    int i=0;//计数
    while(1)
    {
        cin>>p[i].name;
        cin>>p[i].score;
        if(p[i].score<0)
        {
            cout<<"输入非法!请重新输入:"<<endl;
            cin>>p[i].score;
        }
        else if(p[i].score==9999)
             break;
        i++;
    }
    sort(p,i); //排序
    double advscore=adv(p,i);//计算平均分
    print(p,i,advscore);//输出
    return 0;
}

通过指针间接排序

某公司有一个包含Person类型结构的Person data[10]数组,需要按姓名对该数组排序

struct Person
{
    string name;
    int age;
};

在真实的程序中,Person 结构可能拥有许多成员,占用大量内存。在这种情况下,排序和移动Person 对象都可能消耗大量的计算资源。因此,可以考虑定义一个辅助数组 Person*pData[10],设置pData[k]的每个元都指向data[k]的对应元素。请编写一个程序,对该指针数组进行排序,这样,当按索引k的升序遍历pData数组时,pData[k]元素即指向按字母顺序的升序排序的Person对象。
 

#include <iostream>
#include <string>
#include <iomanip>
using namespace std;
struct Person
{
    string name;
    int age;
};

void sort(Person *p,int n)
{
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n-i-1;j++)
        {
            if(p[j].name>p[j+1].name)
            {
                Person x=p[j];
                p[j]=p[j+1];
                p[j+1]=x;
            }
        }
    }
}
int main()
{
    Person data[10];
    Person *pData[10];

    cout<<"请输入员工的姓名和年龄:"<<endl;
    for(int i=0;i<10;i++)
        cin>>data[i].name>>data[i].age;

    //pData[k]的每个元都指向data[k]的对应元素
    for(int i=0;i<10;i++)
        pData[i]=&data[i];

    sort(*pData,10);

    cout<<'t'<<"姓名排序表"<<endl;
    cout<<"----------------------------"<<endl;
    for(int i=0;i<10;i++)
        cout<<setw(15)<<left<<pData[i]->name<<pData[i]->age<<endl;
    return 0;
}
/*
Alice 31
Lucy 20
Linda 25
Cathy 26
Caroline 23
Bob 27
John 29
Peter 24
Justin 23
Jack 30*/

通过指针间接排序(2)

请编写一个程序,解决刚才那题提出的问题,但是指针数组现在应该指向按年龄降序排序的数据数组。

#include <iostream>
#include <string>
#include <iomanip>
using namespace std;
struct Person
{
    string name;
    int age;
};

void sort(Person *p,int n)
{
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n-i-1;j++)
        {
            if(p[j].age<p[j+1].age)
            {
                Person x=p[j];
                p[j]=p[j+1];
                p[j+1]=x;
            }
        }
    }
}
int main()
{
    Person data[10];
    Person *pData[10];

    cout<<"请输入员工的姓名和年龄:"<<endl;
    for(int i=0;i<10;i++)
        cin>>data[i].name>>data[i].age;

    //pData[k]的每个元都指向data[k]的对应元素
    for(int i=0;i<10;i++)
        pData[i]=&data[i];

    sort(*pData,10);

    cout<<'t'<<"姓名排序表"<<endl;
    cout<<"----------------------------"<<endl;
    for(int i=0;i<10;i++)
        cout<<setw(15)<<left<<pData[i]->name<<pData[i]->age<<endl;
    return 0;
}

馅饼吃货可知否

在统计作业中,一组值得众数是经常出现得值。请编写一个程序,确定大多数人每年吃多少块馅饼。设置一个可以保存30人得回答的整数数组,输入每个人所说的一年中所吃的馅饼块数。然后编写一个函数来查找这30个值得众数,这将是大多数人吃的馅饼的块数。查找和返回众数得函数应该接受两个实参,其中一个是整数数组,另一个是指示数组中有多少元素。
 

#include <iostream>

using namespace std;
int Mode(int *s,int n)
{
    int num=1,x=1;
    //从大到小排序
    for(int i=0;i<n-1;i++)
    {
        for(int j=0;j<n-i-1;j++)
        {
            if(*(s+j)<*(s+j+1))
            {
                int tmp=*(s+j);
                *(s+j)=*(s+j+1);
                *(s+j+1)=tmp;
            }
        }
    }
    int m;//记录众数的值
    int current=0;//当前正在访问的数字的个数
    int most=0;//目前的众数个数
    for(int i=0;i<n;i++)
    {
        current++;
        if(*(s+i)!=*(s+i+1) || i==n-1)
        {
            if(current>most)
            {
                most=current;
                m=*(s+i);
            }
            current=0;
        }
    }
    return m;
}

int main()
{
    int *p=new int[30];
    cout<<"请输入每年吃的馅饼块数:"<<endl;
    int i=0;
    while(1)
    {
        cin>>*(p+i);
        if(*(p+i)<0)
        {
            cout<<"输入有误!不能为负数!请重新输入:"<<endl;
            cin>>*(p+i);
        }
        else if(*(p+i)==0000)
            break;
        i++;
    }
    cout<<"所吃馅饼的众数为:"<<Mode(p,i);
    return 0;
}

五道编程题每道17分(我自己编的,凑个整满分100分)

 

最后

这次写博客花了几个小时,比对资料,运行出结果...

咱也不为了没用的赞和评论

只是想把自己学到的知识分享给大家,顺便自己也捋顺一遍

最后,上面的题你们都拿了多少分呢?可以在下面投票

这次写了一万多字(草稿已保存 17:23:49 共 10309 字),还有不懂的也可以私信我

拜拜

 

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>