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

指针广泛应用于 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. [GPT] ./ssh/known_hosts 是什么

      ~/.ssh/known_hosts 是一个SSH客户端用来存储已知的远程主机的公钥的文件,这些公钥用于验证连接到远程主机时它们是否为真实可信的主机. 当你首次通过SSH连接到一个新的远程主机时, ...

  2. IIncrementalGenerator 增量 Source Generator 生成代码应用 将构建时间写入源代码

    本文将和大家介绍一个 IIncrementalGenerator 增量 Source Generator 生成代码技术的应用例子,将当前的构建时间写入到代码里面.这个功能可以比较方便实现某些功能的开关 ...

  3. dotnet 使用 TaskTupleAwaiter 同时等待多个任务简化代码写法

    在某些业务逻辑下,需要同时等待多个任务执行完成,才能继续往下执行后续逻辑.等待任务执行的逻辑,大部分情况下需要使用到 Task.WhenAll 方法,代码行数不少.另外,在需要获取多个异步任务的返回值 ...

  4. 2018-8-10-win10-uwp-如何开始写-uwp-程序

    title author date CreateTime categories win10 uwp 如何开始写 uwp 程序 lindexi 2018-08-10 19:16:50 +0800 201 ...

  5. C# 采集知网

    采集知网 WebClient /// <summary> /// 支持 Session 和 Cookie 的 WebClient. /// </summary> public ...

  6. [Python急救站]基于Transformer Models模型完成GPT2的学生AIGC学习训练模型

    为了AIGC的学习,我做了一个基于Transformer Models模型完成GPT2的学生AIGC学习训练模型,指在训练模型中学习编程AI. 在编程之前需要准备一些文件: 首先,先win+R打开运行 ...

  7. 微信小程序使用微信云托管添加自定义域名并转发到pexels.com

    背景:我要在小程序上显示pexels.com上的图片,然后我得先把pexels.com的域名添加到小程序的request合法域名中,但是pexels.com是国外的,在国内没有备案所以添加不了.解决方 ...

  8. JS实现下拉框切换和tab标签切换

    现在商城网页上会有下拉框切换内容,是如何实现的呢,研究了一天,在调整js代码和查找bug.最终完成了自己想要的效果,我没有写CSS样式,只是实现了基本功能,如果对你有所帮助,可以自己写css,使其更加 ...

  9. JOISC2019 题解

    \(\text{By DaiRuiChen007}\) Contest Link A. Examination Problem Link 题目大意 有 \(n\) 个二元组 \((a,b)\) 和 \ ...

  10. C语言:渔夫捕鱼算法问题

    题目:渔夫捕鱼 A,B,C,D,E五个渔夫夜间合伙捕鱼,,第二天清A先醒来,他把鱼均分五份,把多余的一条扔回湖中,便拿了自己的一份回家了,B醒来后,也把鱼均分五份,把多余的一条扔回湖中,便拿了自己的一 ...