C++温习笔记(慕羽★)——指针及相关内容(下)

   本系列文章用于记录,近期温习C++过程中的一些笔记内容,本文主要记录指针相关的内容。


   全部内容分为上下两篇



   12、数组指针(行指针)

   (1)一维数组

   将一个整型变量加1后,其值将增加1。但是,将指针变量(地址的值)加1后,增加的量等于它指向的数据类型的字节数。

   ①数组在内存中占用的空间是连续的。

   ②C++将数组名解释为数组第0个元素的地址。

   ③数组第0个元素的地址和数组首地址的取值是相同的。

   ④数组第n个元素的地址是:数组首地址+n

   ⑤C++编译器把 数组名[下标] 解释为 *(数组首地址+下标)

   数组是占用连续空间的一块内存,在多数情况下,数组名被解释为数组第0个元素的地址。C++操作这块内存有两种方法:数组解释法和指针表示法,它们是等价的。但是,将sizeof运算符用于数据名时,将返回整个数组占用内存空间的字节数。可以修改指针的值,但数组名是常量,不可修改。

   示例12

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

int main()
{
    
    
	int a[5] = {
    
     6 , 66 , 666 , 6666 , 66666 };

	//输出地址
	cout << "a的值是:" << (long long)a << endl;
	cout << "&a的值是:" << (long long)&a << endl;

	cout << "a[0]的地址是:" << (long long)&a[0] << endl;
	cout << "a[1]的地址是:" << (long long)&a[1] << endl;


	int* p = a;
	cout << "p的值是:" << (long long)p << endl;
	cout << "p+0的值是:" << (long long)(p + 0) << endl;
	cout << "p+1的值是:" << (long long)(p + 1) << endl;

	//输出值
	cout << "a[0]的值是:" << a[0] << endl;
	cout << "a[1]的值是:" << a[1] << endl;

	cout << "*(p+0)的值是:" << *(p + 0) << endl;
	cout << "*(p+1)的值是:" << *(p + 1) << endl;

}

   示例12输出结果

a的值是:557739014152
&a的值是:557739014152
a[0]的地址是:557739014152
a[1]的地址是:557739014156
p的值是:557739014152
p+0的值是:557739014152
p+1的值是:557739014156
a[0]的值是:6
a[1]的值是:66
*(p+0)的值是:6
*(p+1)的值是:66

   一维数组用于函数的参数时,只能传数组的地址,并且必须把数组长度也传进去,除非数组中有最后一个元素的标志。

   书写方法有两种:

   void func(int* arr, int len);

   void func(int arr[], int len);

   在函数中,可以用数组表示法,也可以用指针表示法,不要对指针名用sizeof运算符,它不是数组名。

   示例13

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

// void func(int *arr,int len)
void func(int arr[],int len)
{
    
    
	for (int ii = 0; ii < len; ii++)
	{
    
    
		cout << "arr[" << ii << "]的值是:" << arr[ii] << endl;              // 用数组表示法操作指针。
		cout << "*(arr+" << ii << ")的值是:" << *(arr + ii) << endl;   // 地址[下标]  解释为  *(地址+下标)。
	}
}

int main()
{
    
    
	int a[] = {
    
    2,8,4,6,7,1,9};
	
	func(a, sizeof(a) / sizeof(int));
}

   示例13输出结果

arr[0]的值是:2
*(arr+0)的值是:2
arr[1]的值是:8
*(arr+1)的值是:8
arr[2]的值是:4
*(arr+2)的值是:4
arr[3]的值是:6
*(arr+3)的值是:6
arr[4]的值是:7
*(arr+4)的值是:7
arr[5]的值是:1
*(arr+5)的值是:1
arr[6]的值是:9
*(arr+6)的值是:9

   声明行指针的语法:数据类型 (*行指针名)[行的大小]; // 行的大小即数组长度。

   几个行指针的示例如下:

   int (*p1)[3]; // p1是行指针,用于指向数组长度为3的int型数组。

   int (*p2)[5]; // p2行指针,用于指向数组长度为5的int型数组。

   double (*p3)[5]; // p3是行指针,用于指向数组长度为5的double型数组。

   一维数组名被解释为数组第0个元素的地址,若对一维数组名+1,得到的是数组中下一个元素的地址;对一维数组名取地址得到的是数组的地址,是行地址,若对一维数组名取地址+1,得到的是下一个数组的地址,如对于int a[10],&a+1得到的是数组a的地址+40,如下所示:

   示例14:

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

int main()
{
    
    
	int a[10];

	cout << "数组a第0个元素的地址:" <<(long long) a << endl;
	cout << "数组a的地址:" << (long long)&a << endl;

	cout << "数组a第0个元素的地址+1:" << (long long)(a + 1) << endl;   // 地址的增加量是4。
	cout << "数组a的地址+1:" << (long long)( & a + 1) << endl;                // 地址的增加量是40。
}

   示例14输出结果:

数组a第0个元素的地址:621223605816
数组a的地址:621223605816
数组a第0个元素的地址+1621223605820
数组a的地址+1621223605856

   在上面的例子中,定义指针和行指针指向数组a的语法如下:

    int* p1 = a;        //指针
	int(*p2)[10] = &a;  //行指针

   (2)多维数组

   二维数组:int bh[2][3] = { {11,12,13},{21,22,23} };

   二维数组名是行地址,bh是二维数组名,该数组有2两元素,每一个元素本身又是一个数组长度为3的整型数组,bh被解释为数组长度为3的整型数组类型的行地址。如果存放bh的值,要用数组长度为3的整型数组类型的行指针。

   int (*p)[3]=bh;

   注意:不能使用 int *p=bh ,会报错

   三维数组: int bh[4][2][3];

   bh是三维数组名,该数组有4元素,每一个元素本身又是一个2行3列的二维数组。bh被解释为2行3列的二维数组类型的二维地址。如果存放bh的值,要用2行3列的二维数组类型的二维指针。

   int (*p)[2][3]=bh;

   其他高维数组以此类推、、、、、、

   接下来看一下如何把二维数组传递给函数,如果要把上面的二维数组bh传给函数,函数的声明如下:

void func(int (*p)[3],int len);
void func(int p[][3],int len);

   示例15:

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

// void func(int(*p)[3], int len)
void func(int p[][3], int len)
{
    
    
	for (int ii = 0; ii < len; ii++)
	{
    
    
		for (int jj = 0; jj < 3; jj++)
			cout << "p[" << ii << "][" << jj << "]=" << p[ii][jj] << "  ";

		cout << endl;
	}
}

int main()
{
    
    
	int bh[2][3] = {
    
     {
    
    77,66,99},{
    
    77,88,66} };

	func(bh, 2);
}

   示例15输出结果:

p[0][0]=77  p[0][1]=66  p[0][2]=99
p[1][0]=77  p[1][1]=88  p[1][2]=66

   13、动态创建数组

   在函数中普通数组在栈上分配内存,栈很小;如果需要存放更多的元素,必须在堆上分配内存。

   动态创建一维数组的语法:数据类型 *指针=new 数据类型[数组长度];

   释放一维数组的语法:delete [] 指针;

   示例16

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

int main()
{
    
    
	int *arr=new int[8];          // 创建8个元素的整型数组。

	for (int ii = 0; ii < 8; ii++)
	{
    
    
		arr[ii] = 100 + ii;                                                                  // 数组表示法。
		cout << "arr[" << ii << "]=" << *(arr + ii) << endl;        // 指针表示法。
	}

	delete[]arr;
}

   示例16输出结果

arr[0]=100
arr[1]=101
arr[2]=102
arr[3]=103
arr[4]=104
arr[5]=105
arr[6]=106
arr[7]=107


   ①动态创建的数组没有数组名,不能用sizeof运算符。

   ② 可以用数组表示法和指针表示法两种方式使用动态创建的数组。

   ③必须使用delete[]来释放动态数组的内存(不能只用delete)。

   ④不要用delete[]来释放不是new[]分配的内存。

   ⑤ 不要用delete[]释放同一个内存块两次(否则等同于操作野指针)。

   ⑥ 对空指针用delete[]是安全的(释放内存后,应该把指针置空nullptr)。

   ⑦ 声明普通数组的时候,数组长度可以用变量,相当于在栈上动态创建数组,并且不需要释放。

   ⑧ 如果内存不足,调用new会产生异常,导致程序中止;如果在new关键字后面加(std::nothrow)选项,则返回nullptr,不会产生异常。

   ⑨ 为什么用delete[]释放数组的时候,不需要指定数组的大小?因为系统会自动跟踪已分配数组的内存。


   14、结构体指针

   结构体是一种自定义的数据类型,用结构体可以创建结构体变量。在C++中,用不同类型的指针存放不同类型变量的地址,这一规则也适用于结构体。如下:

   struct st_muyu muyu; // 声明结构体变量muyu。

   struct st_muyu *pst=&muyu; // 声明结构体指针,指向结构体变量muyu。

   通过结构体指针访问结构体成员,有两种方法:

   (*指针名).成员变量名 // (*pst).name和(*pst).age

   指针名->成员变量名 // pst->name和*pst->age

   在第一种方法中,圆点.的优先级高于*,(*指针名)两边的括号不能少。如果去掉括号写成(指针名).成员变量名,那么相当于(指针名.成员变量名),意义就完全不一样了。在第二种方法中,->是一个新的运算符。上面的两种方法是等效的,程序员通常采用第二种方法,更直观。与数组不一样的是,结构体变量名没有被解释为地址。

   如果要把结构体传递给函数,实参取结构体变量的地址,函数的形参用结构体指针。如果不希望在函数中修改结构体变量的值,可以对形参加const约束。


   15、引用

   引用变量是C++新增的复合类型,引用是已定义的变量的别名。引用的主要用途是用作函数的形参和返回值。

   引用的本质是指针常量的伪装,编译器会把引用解释为指针,引用和指针从本质上来说没有区别

   声明/创建引用的语法:数据类型 &引用名=原变量名;

   ① 引用的数据类型要与原变量名的数据类型相同。

   ② 引用名和原变量名可以互换,它们值和内存单元是相同的。

   ③ 必须在声明引用的时候初始化,初始化后不可改变。

   ④ C和C++用&符号来指示/取变量的地址,C++给&符号赋予了另一种含义。

   示例17

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

int main()
{
    
    
	// 声明 / 创建引用的语法:数据类型 & 引用名 = 原变量名;
	int a = 3;          // 声明普通的整型变量。
	int& ra = a;      // 创建引用ra,ra是a的别名。

	cout << " a的地址是:" << &a << ", a的值是:" << a << endl;
	cout << "ra的地址是:" << &ra << ",ra的值是:" << ra << endl;

	ra = 5;

	cout << " a的地址是:" << &a << ", a的值是:" << a << endl;
	cout << "ra的地址是:" << &ra << ",ra的值是:" << ra << endl;
}

   示例17输出结果

a的地址是:00000086A498F514, a的值是:3
ra的地址是:00000086A498F514,ra的值是:3
 a的地址是:00000086A498F514, a的值是:5
ra的地址是:00000086A498F514,ra的值是:5

   把函数的形参声明为引用,调用函数的时候,形参将成为实参的别名。这种方法也叫按引用传递或传引用。引用的本质是指针,传递的是变量的地址,在函数中,修改形参会影响实参。下面的例子给出了传值、传地址、传引用的示例:

   示例18

#include <iostream>
using namespace std;

void fout(int num, string str)               //传值
{
    
    
	cout << num << "   " << str << endl;
	num = 66;  str = "MY";
}

void fout2(int* num, string* str)           //传地址
{
    
    
	cout << *num << "   " << *str << endl;   
	*num = 66;  *str = "MY";
}

void fout3(int& num, string& str)           //传引用
{
    
    
	cout << num << "   " << str << endl;
	num = 66;  str = "MY";
}

int main()
{
    
    
	int no; string st;

	no = 99; st = "慕羽";
	fout(no, st);
	cout << no << "   " << st << endl;

	cout << endl;

	no = 99; st = "慕羽";
	fout2(&no, &st);
	cout << no << "   " << st << endl;

	cout << endl;

	no = 99; st = "慕羽";
	fout3(no, st);
	cout << no << "   " << st << endl;


}

   示例18输出结果

99   慕羽
99   慕羽

99   慕羽
66   MY

99   慕羽
66   MY

   传值、传地址、传引用的差异可概括成下表所示,相比之下,传引用更简洁有效,

   《C++ Primer Plus》中给出了传值、传地址和传引用的指导原则

   (1)如果不需要在函数中修改实参

   如果实参很小,如C++内置的数据类型或小型结构体,则按值传递。

   如果实参是数组,则使用const指针,因为这是唯一的选择(没有为数组建立引用的说法)。

   如果实参是较大的结构,则使用const指针或const引用。

   如果实参是类,则使用const引用,传递类的标准方式是按引用传递(类设计的语义经常要求使用引用)。

   (2)如果需要在函数中修改实参

   如果实参是内置数据类型,则使用指针。只要看到func(&x)的调用,表示函数将修改x。

   如果实参是数组,则只能使用指针。

   如果实参是结构体,则使用指针或引用。

   如果实参是类,则使用引用。

   当然,这只是一些指导原则,很可能有充分的理由做出其他的选择。




   此外,传引用不必使用二级指针,如下面的例子所示:

   示例19

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

void func1(int** p)      // 传地址,实参是指针的地址,形参是二级指针。
{
    
    
	*p = new int(3);       // p是二级指针,存放指针的地址。
	cout << "func1内存的地址是:" << *p << ",内存中的值是:" << **p << endl;
}

void func2(int*& p)     // 传引用,实参是指针,形参是指针的别名。
{
    
    
	p = new int(3);         // p是指针的别名。
	cout << "func2内存的地址是:" << p << ",内存中的值是:" << *p << endl;
}

int main()
{
    
    
	int* p = nullptr;    // 存放在子函数中动态分配内存的地址。

	//func1(&p);      // 传地址,实参填指针p的地址。
	func2(p);      // 传引用,实参填指针p。

	cout << "main 内存的地址是:" << p << ",内存中的值是:" << *p << endl;

	delete p;
}

   示例19输出结果

func2内存的地址是:000001CB275010F0,内存中的值是:3
main 内存的地址是:000001CB275010F0,内存中的值是:3

   如果引用的数据对象类型不匹配,当引用为const时,C++将创建临时变量,让引用指向临时变量。如果函数的实参不是左值或与const引用形参的类型不匹配,那么C++将创建正确类型的匿名变量,将实参的值传递给匿名变量,并让形参来引用该变量。

   使用const可以避免无意中修改数据的编程错误。使用const使函数能够处理const和非const实参,否则将只能接受非const实参。使用const,函数能正确生成并使用临时变量。

   左值是可以被引用的数据对象,可以通过地址访问它们,例如:变量、数组元素、结构体成员、引用和解引用的指针。非左值包括字面常量(用双引号包含的字符串除外)和包含多项的表达式。

   比如,在下面的例子中,由于函数func的形参使用了const修饰,所以调用该函数时,给定的实参可以是常量,而函数func2在调用时,其实参不能是常量,需要先将常量999和“哈哈哈”存放在变量中,再将变量作为实参传给func2,否则会报错。

   示例20

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

void func(const int& no, const string& str)    // 传引用。
{
    
    
	cout << "华灯初上" <<" " << no << "大道不孤:" << " " << str << endl;
}

void func2(int& no,  string& str)    // 传引用。
{
    
    
	cout << "华灯初上" << " " << no << "大道不孤:" << " " << str << endl;
}


int main()
{
    
    
	func(999,"哈哈哈");

	int a = 999;
	string st = "哈哈哈";

	func2(a, st);
}

   示例20输出结果

华灯初上 999大道不孤: 哈哈哈
华灯初上 999大道不孤: 哈哈哈

   传统的函数返回机制与值传递类似。函数的返回值被拷贝到一个临时位置(寄存器或栈),然后调用者程序再使用这个值。如double m=sqrt(81); sqrt(81)的返回值9被拷贝到临时的位置,然后赋值给m。如果返回引用则不会拷贝内存。

   语法:返回值的数据类型& 函数名(形参列表);

   如果返回局部变量的引用,其本质是野指针,后果不可预知。可以返回函数的引用形参、类的成员、全局变量、静态变量。 返回引用的函数是被引用的变量的别名,将const用于引用的返回类型。

   示例21

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

int func( int& num)    // 传引用,函数返回值为int型变量。
{
    
     
	num++;
	return num;
}

int &func2(int& num)    // 传引用,返回值为引用。
{
    
       
	num++;
	return num;
}


int main()
{
    
    

    int n = 999;
	int n1 = func(n);
	int &n2= func2(n);
	
	cout << "n1:   " << n1 << "  " << "n2:   " << n2;
}

   示例21输出结果

n1:   1000  n2:   1001

   16、this指针

   如果类的成员函数中涉及多个对象,在这种情况下需要使用this指针。this指针存放了对象的地址,它被作为隐藏参数传递给了成员函数,指向调用成员函数的对象(调用者对象)。每个成员函数(包括构造函数和析构函数)都有一个this指针,可以用它访问调用者对象的成员。(可以解决成员变量名与函数形参名相同的问题)

   *this可以表示对象。

   如果在成员函数的括号后面使用const,那么将不能通过this指针修改成员变量。

   示例22

   User_information.h文件:

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。ragma once

class User_information
{
    
    
private:

    //属性
    string name_;
    int id_;

public:
    //构造函数
    User_information() {
    
     cout<<endl << "这仅仅只是一个构造函数" << endl << endl; }

    User_information(string const& name, int const& ID) :name_(name), id_(ID)
    {
    
    
        cout<<endl << "这个构造函数利用初始化列表对属性进行了初始化" << endl << endl;
    }

    //析构函数
    ~User_information() {
    
     cout << endl << "这仅仅只是一个析构函数" << endl; }

    //成员函数
    void show_information() const
    {
    
    
        cout << "Name:" << "   " << name_ << endl;
        cout << "Id:" << "   " << id_ << endl;
    }

    void reset_name(string& re_name) {
    
     name_ = re_name; }

    void reset_id(int& re_id) {
    
     id_ = re_id; }

    string get_name() const {
    
     return name_; }

    int get_id() const {
    
     return id_; }

    User_information & compare_id(User_information & user)
    {
    
    
        if (user.id_ > id_) return user;
        return *this;
    }

};

   classtest.cpp文件:

#include "User_information.h"

int main()
{
    
    
   
    string r_name = "雨夜聆风";
    int r_id = 666666;

    User_information User;    // 创建User_information对象Muyu,不设置任何初始值。
    User.reset_name(r_name);  // 修改属性值
    User.reset_id(r_id);

    cout << "Name:" << "   " << User.get_name() << endl;
    cout << "Id:" << "   " << User.get_id() << endl;

    User_information Muyu("慕羽",999999);    // 创建User_information对象Muyu,并设置初始值。
   
    Muyu.show_information();

    User_information& id_max = User.compare_id(Muyu);

    cout<<endl << id_max.get_name() << "的id值更大!!!"<<endl;


}

   示例22输出结果


这仅仅只是一个构造函数

Name:   雨夜聆风
Id:   666666

这个构造函数利用初始化列表对属性进行了初始化

Name:   慕羽
Id:   999999

慕羽的id值更大!!!

这仅仅只是一个析构函数

这仅仅只是一个析构函数


猜你喜欢

转载自blog.csdn.net/qq_44339029/article/details/129822642