指针是一个变量,用于存储对象的内存地址

指针广泛应用于 C 和 C++:

  • 在堆上分配新对象
  • 通过参数将某些函数传递给其他函数
  • 迭代/遍历数组或其他数据结构的元素
    int* p = nullptr; // declare pointer and initialize it
// so that it doesn't store a random address
int i = 5;
p = &i; // assign pointer to address of object
int j = *p; // dereference p to retrieve the value at its address
    MyClass* mc = new MyClass(); // allocate object on the heap
mc->print(); // access class member
delete mc; // delete object (please don't forget!)
    // declare a C-style string. Compiler adds terminating '\0'.
const char* str = "Hello world"; const int c = 1;
const int* pconst = &c; // declare a non-const pointer to const int
const int c2 = 2;
pconst = &c2; // OK pconst itself isn't const
const int* const pconst2 = &c;
// pconst2 = &c2; // Error! pconst2 is const.

当定义函数时,尽量将指针参数标记为 const,除非你期望函数能够修改传入的对象。一般来讲,const 引用更适合用来传递对象给函数,除非对象可能为 nullptr

函数指针使函数能够被传递给其他函数,在 C 风格代码中可用于回调。现代 C++ 使用 lambda 表达式来达到同样地目的。

C 风格代码示例:

#include <iostream>
#include <string> class MyClass
{
public:
int num;
std::string name;
void print() { std::cout << name << ":" << num << std::endl; }
}; // Accepts a MyClass pointer
void func_A(MyClass* mc)
{
// Modify the object that mc points to.
// All copies of the pointer will point to
// the same modified object.
mc->num = 3;
} // Accepts a MyClass object
void func_B(MyClass mc)
{
// mc here is a regular object, not a pointer.
// Use the "." operator to access members.
// This statement modifies only the local copy of mc.
mc.num = 21;
std::cout << "Local copy of mc:";
mc.print(); // "Erika, 21"
} int main()
{
// Use the * operator to declare a pointer type
// Use new to allocate and initialize memory
MyClass* pmc = new MyClass{ 108, "Nick" }; // Prints the memory address. Usually not what you want.
std:: cout << pmc << std::endl; // Copy the pointed-to object by dereferencing the pointer
// to access the contents of the memory location.
// mc is a separate object, allocated here on the stack
MyClass mc = *pmc; // Declare a pointer that points to mc using the addressof operator
MyClass* pcopy = &mc; // Use the -> operator to access the object's public members
pmc->print(); // "Nick, 108" // Copy the pointer. Now pmc and pmc2 point to same object!
MyClass* pmc2 = pmc; // Use copied pointer to modify the original object
pmc2->name = "Erika";
pmc->print(); // "Erika, 108"
pmc2->print(); // "Erika, 108" // Pass the pointer to a function.
func_A(pmc);
pmc->print(); // "Erika, 3"
pmc2->print(); // "Erika, 3" // Dereference the pointer and pass a copy
// of the pointed-to object to a function
func_B(*pmc);
pmc->print(); // "Erika, 3" (original not modified by function) delete(pmc); // don't forget to give memory back to operating system!
// delete(pmc2); //crash! memory location was already deleted
}

指针算法和数组

指针和数组是紧密联系的,当一个数组以值传递给函数时,传递的是指向第一个元素的指针,下面的示例演示了指针和数组的重要属性:

  • sizeof 操作符返回数组以字节为单位的大小。
  • 要确定元素的个数,可以用元素的大小值去除数组的总大小。
  • 当数组传递给函数时,将退化为指针类型。
  • sizeof 操作符用在指针上时返回时指针的大小,在 x86 系统上是4字节,在 x64 系统上是8字节。
#include <iostream>

void func(int arr[], int length)
{
// returns pointer size. not useful here.
size_t test = sizeof(arr); for(int i = 0; i < length; ++i)
{
std::cout << arr[i] << " ";
}
} int main()
{ int i[5]{ 1,2,3,4,5 };
// sizeof(i) = total bytes
int j = sizeof(i) / sizeof(i[0]);
func(i,j);
}

const 指针可以进行算术操作以指向不同的内存地址: ++, +=, -= and --

这对于数组尤其是无类型的缓冲区特别有用:

  • void* 以一个 char (1 byte)的大小递增。
  • 有类型的指针以类型的大小递增。

以下示例演示了指针如何通过算数操作 Windows 位图的像素点。

注意 newdelete 的使用,以及解引用操作。

#include <Windows.h>
#include <fstream> using namespace std; int main()
{ BITMAPINFOHEADER header;
header.biHeight = 100; // Multiple of 4 for simplicity.
header.biWidth = 100;
header.biBitCount = 24;
header.biPlanes = 1;
header.biCompression = BI_RGB;
header.biSize = sizeof(BITMAPINFOHEADER); constexpr int bufferSize = 30000;
unsigned char* buffer = new unsigned char[bufferSize]; BITMAPFILEHEADER bf;
bf.bfType = 0x4D42;
bf.bfSize = header.biSize + 14 + bufferSize;
bf.bfReserved1 = 0;
bf.bfReserved2 = 0;
bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); //54 // Create a gray square with a 2-pixel wide outline.
unsigned char* begin = &buffer[0];
unsigned char* end = &buffer[0] + bufferSize;
unsigned char* p = begin;
constexpr int pixelWidth = 3;
constexpr int borderWidth = 2; while (p < end)
{
// Is top or bottom edge?
if ((p < begin + header.biWidth * pixelWidth * borderWidth)
|| (p > end - header.biWidth * pixelWidth * borderWidth)
// Is left or right edge?
|| (p - begin) % (header.biWidth * pixelWidth) < (borderWidth * pixelWidth)
|| (p - begin) % (header.biWidth * pixelWidth) > ((header.biWidth - borderWidth) * pixelWidth))
{
*p = 0x0; // Black
}
else
{
*p = 0xC3; // Gray
}
p++; // Increment one byte sizeof(unsigned char).
} ofstream wf(R"(box.bmp)", ios::out | ios::binary); wf.write(reinterpret_cast<char*>(&bf), sizeof(bf));
wf.write(reinterpret_cast<char*>(&header), sizeof(header));
wf.write(reinterpret_cast<char*>(begin), bufferSize); delete[] buffer; // Return memory to the OS.
wf.close();
}

void* 指针

void 指针指向原始内存地址。有时需要使用 void* 指针,例如在 C++ 代码和 C 函数之间传递时。

当有类型的指针转换为 void 指针时,保存的内存地址是不变的。然而,类型信息会丢失,所以你不能进行加减操作。内存地址是可以转型(cast)的,例如,从 MyClass*void* 然后回到 MyClass*

这样的操作本身就是容易出错的,必须很谨慎。现代 C++ 几乎在所有场景中都不建议使用 void 指针。


//func.c
void func(void* data, int length)
{
char* c = (char*)(data); // fill in the buffer with data
for (int i = 0; i < length; ++i)
{
*c = 0x41;
++c;
}
} // main.cpp
#include <iostream> extern "C"
{
void func(void* data, int length);
} class MyClass
{
public:
int num;
std::string name;
void print() { std::cout << name << ":" << num << std::endl; }
}; int main()
{
MyClass* mc = new MyClass{10, "Marian"};
void* p = static_cast<void*>(mc);
MyClass* mc2 = static_cast<MyClass*>(p);
std::cout << mc2->name << std::endl; // "Marian" // use operator new to allocate untyped memory block
void* pvoid = operator new(1000);
char* pchar = static_cast<char*>(pvoid);
for(char* c = pchar; c < pchar + 1000; ++c)
{
*c = 0x00;
}
func(pvoid, 1000);
char ch = static_cast<char*>(pvoid)[0];
std::cout << ch << std::endl; // 'A'
operator delete(p);
}

函数指针

C 风格代码中,函数指针主要用来将一个函数传递给另一个函数。这允许调用方可以定制函数的行为而不去修改他。

现代 C++ 中,lambda 表达式提供了同样地功能并保证了更高的安全性和其他优点。

函数指针声明规定了指向的函数必须要有如下签名:

// Declare pointer to any function that...

// ...accepts a string and returns a string
string (*g)(string a); // has no return value and no parameters
void (*x)(); // ...returns an int and takes three parameters
// of the specified types
int (*i)(int i, string s, double d);

示例:

#include <iostream>
#include <string> using namespace std; string base {"hello world"}; string append(string s)
{
return base.append(" ").append(s);
} string prepend(string s)
{
return s.append(" ").append(base);
} string combine(string s, string(*g)(string a))
{
return (*g)(s);
} int main()
{
cout << combine("from MSVC", append) << "\n";
cout << combine("Good morning and", prepend) << "\n";
}

原始指针 [raw pointers]的更多相关文章

  1. C和指针 (pointers on C)——第十章:结构体和联合(上)

    第十章 结构和联合 这个部分先介绍了.运算符,能够供直接訪问,还介绍了->运算符,它取代结构体指针的间接訪问操作(*struct).xxx 这一章新手理解起来不算太难,没有学过操作系统的话理解位 ...

  2. C和指针 (pointers on C)——第十四章:预处理器

    第十四章 预处理器 我跳过了先进的指针主题的章节. 太多的技巧,太学科不适合今天的我.但我真的读,读懂.假设谁读了私下能够交流一下.有的小技巧还是非常有意思. 预处理器这一章的内容.大家肯定都用过.什 ...

  3. C和指针 (pointers on C)——第三章——数据

    第三章 数据 本章是非常重要的,在特定范围内使用.链接属性.存储类型.const.extern和statickeyword使用.几乎所有的公司是C++在采访的第一个问题. 总结: 具有external ...

  4. C和指针 (pointers on C)——第一章:高速启动

    大多数人并不认为有几类人猿学校计划非常赞同C学习好,然后看多本书. 仅仅作为读书笔记写.有时还包括一些题目和答案. 这样的公开栏,这两种玉引砖敲,对于自勉,鼓励王! 第一章:手 我每次都是复习的来写. ...

  5. Android的原始资源Raw和Assert资源的使用-android学习之旅(五十七)

    代码示例 public class MainActivity extends Activity{ MediaPlayer mediaPlayer1,mediaPlayer2; @Override pr ...

  6. C和指针 (pointers on C)——第七章:函数(上)

    第七章 函数 这一章对于有一定C的基础的人有一定优秀代码风格的人来说,并非非常虐.关于stdarg宏可能有些陌生.它负责可变參数列表的定义. 总结: 新式风格和旧式风格就不要提了.八百年前的事情. 函 ...

  7. C和指针 (pointers on C)——第十二章:利用结构和指针

    第十二章 利用结构和指针 这章就是链表.先单链表,后双向链表. 总结: 单链表是一种使用指针来存储值的数据结构.链表中的每一个节点包括一个字段,用于指向链表的下一个节点. 有一个独立的根指针指向链表的 ...

  8. C和指针 (pointers on C)——第四章:语句(上)

    第四章--语句(上) 总结总结!!! C没有布尔类型,所以在一些逻辑推断时候必须用整型表达式,零值为假,非零值为真. for比while把控制循环的表达式收集起来放在一个地方,以便寻找. do语句比w ...

  9. C和指针 (pointers on C)——第五章:操作符和表达式

    第五章 操作符和表达式 这一章假设没做过玩过单片机.汇编的话,读起来可能比較吃力,尤其是在移位运算符.位运算符应用上.另外多注意一下左值和右值的理解. 总结: 算术操作符.赋值操作符.关系操作符.条件 ...

  10. C和指针 (pointers on C)——第十一章:动态内存分配(下)习题

    1.编写calloc,内部用malloc. void *calloc (size_t n, size_t size) { char * memory; memory =(char*) malloc(n ...

随机推荐

  1. 2019-10-7-WPF-will-break-when-an-exception-be-throw-in-the-StylusPlugIn

    title author date CreateTime categories WPF will break when an exception be throw in the StylusPlugI ...

  2. 读取 k8s 存储在 etcd 上的数据

    读取 k8s 存储在 etcd 上的数据 Etcd Assistant 是一款 Etcd 可视化管理工具,便捷高效地操作您的 etcd 集群:支持多种键的视图:管理租约.用户.角色和权限. etcd是 ...

  3. Raft 共识算法3-日志复制

    Raft 共识算法3-日志复制 Raft算法中译版地址:https://object.redisant.com/doc/raft中译版-2023年4月23日.pdf 英原论文地址:https://ra ...

  4. K8s应用---配置管理中心configmap和Secret(13)

    一.Configmap概述 1.1 什么是configmap Configmap 是 k8s 中的资源对象,用于保存非机密性的配置的,数据可以用 key/value 键值对的形式保存,也可通过文件的形 ...

  5. 数据可视化之matplotlib模块

    一.简介 Matplotlib是一个强大的Python绘图和数据可视化的工具包.数据可视化也是我们数据分析的最重要的工作之一,可以帮助我们完成很多操作,例如:找出异常值.必要的一些数据转换等.完成数据 ...

  6. 数据可视化之pyecharts模块

    1.简介 pyecharts 是一个用于生成 Echarts 图表的类库. Echarts 是百度开源的一个数据可视化 JS 库.主要用于数据可视化. # 安装 # 安装v1版本之上的有一些报错 py ...

  7. 从原始边列表到邻接矩阵Python实现图数据处理的完整指南

    本文分享自华为云社区<从原始边列表到邻接矩阵Python实现图数据处理的完整指南>,作者: 柠檬味拥抱. 在图论和网络分析中,图是一种非常重要的数据结构,它由节点(或顶点)和连接这些节点的 ...

  8. VueJs创建网易音乐播放器和vueJs常见错误处理

    学习vuejs之后,总得自己上手写一些完整的应用,才能够更加了解各个结构与组件之间的运用. 下面从最基础的准备工作开始: 用vue-cli搭建vue应用:先确保自己已经安装Node环境. (1)Win ...

  9. CSS自适应网页(CSS第一篇)

    ​CSS的属性: 用浏览器自带的审查元素对一些页面进行调整,快捷键是F12. 网页允许宽度自适应: 在代码的头部加入一行viewport元标签. <meta name="viewpor ...

  10. C#/.NET/.NET Core优秀项目和框架2024年4月简报

    前言 公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的介绍.功能特点.使用方式以及部分功能截图 ...