Contents

c++(six_1)

数组 指针与字符串

数组

  1. 数组的概念

    数组是具有一定顺序关系的若干相同类型变量的集合体(数组可以由除void型以外的任何类型构成),组成数组的变量称为该数组的元素

  2. 数组属于构造类型

注意:

声明一个数组,应该包括以下几个方面

  1. 确定数组的名称;
  2. 确定数组元素的类型;
  3. 确定数组的结构(包括数组维数,每一位的大小等)。

数组的说明与使用

  1. 一维数组的声明与引用

    1. 一维数组的声明

      类型说明符  数组名[ 常量表达式 ]
      

      数组名的构成方法与一般变量名相同。

      例如: int a[10];

      表示 a 为整型数组,有10个元素:a[0]…a[9]

      注意:常量表达式其值必须是正整数。

  2. 引用

    必须先声明,后使用。

    只能逐个引用数组元素,而不能一次引用整个数组(除传地址)

    例如:a[0]=a[5]+a[7]-a[2*3]

例: 一维数组的声明与引用

#include <iostream.h>
void main 
{
	int A[10],B[10];
	int i;
	for(i=0;i<10;i++)
	{
	A[i]=i*2-1;
	B[10-i-1]=A[i];
	}
	for(i=0;i<10;i++)
	{
	   cout<<"A["<<i
           <<"]="<<A[i];
	   cout<<" B["<<i
             <<"]="
             <<B[i]<<endl;
	}
}

注意:

  1. 如果数组越界,运行时有时会得到提示,但有时却得不到任何提示,不可预期的结果会悄悄发生
  2. 数组元素的下标表达式可以是任意合法的算术表达式,其结果必须是整数

数组的存储和初始化

  1. 一维数组的存储顺序

    数组元素在内存中连续顺次存放,它们的地址是连续的

    例如:具有10个元素的数组 a,在内存中的存放次序如下:

    https://cdn.jsdelivr.net/gh/adan-ning/images/202404202046744.png

    a:数组名字是数组首元素的内存地址。数组名是一个常量,不能被赋值。

  2. 一维数组的初始化

    可以在编译阶段使数组得到初值:

    1. 在声明数组时对数组元素赋以初值。

      例如: int a[10]={0,1,2,3,4,5,6,7,8,9};

    2. 可以只给一部分元素赋初值

      例如: int a[10]={0,1,2,3,4};

    3. 在对全部数组元素赋初值时,可以不指定数组长度

      例如: int a[ ]={1,2,3,4,5}

    4. 数组也可以声明为常量.

      const float a[5]={1.0,2.0};

    5. 下面的代码对数组进行初始化是错误的:

      int array1[5]={ 1,2,3,4,5,6};    
      //error:初始化值个数多于数组元素个数
      int array2[5] = {1,,2,3,4} ;
      //error:初始化值不能省略中间
      int array3[5] = {, ,1,2,3};
      //error:初始化值不能省略前面
      void main( )
      {     
          //……
       }
      

    例 :用数组来处理求Fibonacci数列问题

    main( )
    {  
        int i;
     	static int f[20]={1,1};/*初始化第0、1个数*/
        for(i=2;i<20;i++) 
         	f[i]=f[i-2]+f[i-1];  /*求第2~19个数*/
    	for(i=0;i<20;i++)   /*输出,每行5个数*/
     	{  
            if(i%5==0) cout<<endl;
      			cout<<f[i];
     	}
    }
    
    //输出结果:
    //1   1   2   3   5
    //8   13   21   34   55
    //89   144   233   377   610
    //987   1597   2584   4181   6765
    
  3. 多维数组的声明及引用

    数据类型  标识符[常量表达式1][常量表达式2] ;
    

    例: int a[5][3];

    表示a为整型二维数组,其中第一维有5个下标(0~4),第二维有3个下标(0~2),数组的元素个数为15,可以用于存放5行3列的整型数据表格。

  4. 二维数组的声明及引用

    1. 二维数组的声明

      类型说明符 数组名[常量表达式][常量表达式];
      

      例如:float a[3][4];

      https://cdn.jsdelivr.net/gh/adan-ning/images/202404202057156.png

    2. 引用

      例如:b[1][2]=a[2][3]/2

      注意:下标不要越界

    3. 存储顺序

      在内存中按行存放,即行优先存储。

      https://cdn.jsdelivr.net/gh/adan-ning/images/202404202059394.png

  5. 多二维数组的声明

    int a [ 2 ] [ 3 ][4];声明一个三维数组

    https://cdn.jsdelivr.net/gh/adan-ning/images/202404202100594.png

  6. 二维数组的初始化

    1. 将所有数据写在一个{}内,按顺序赋值(如果是基本数据类型的数组,初始化过程就是给数组元素赋值,对于对象数组,每一个元素都是一个类的实例,将调用类的构造函数)

      例如:

      static int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
      
    2. 可以对部分元素赋初值

      例如:

      static int a[3][4]={{1},{0,6},{0,0,1,1}};
      
    3. 如果给出全部元素的初值,第一维的下标个数可以不用显示说明

      static int a[2][3]={1 , 0 , 0 , 0 , 1 , 0};
      //或
      static int a[  ][3]={1 , 0 , 0 , 0 , 1 , 0};
      

    例:

    # include<iostream.h>
    void main()
    {
    	static int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    	static int b[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
    	static int c[3][4] = {{1},{0,6},{0,0,1,1}};
        static int d[3][4] = {1,0,6,0,0,1,1};
        int i,j;
    	for (i=0;i<3;i++)
      		for (j =0;j<4;j++)
          		cout<<a[i][j]<<"  ";
           		cout<<endl;
    	for (i=0;i<3;i++)
      		for (j =0;j<4;j++)
           		cout<<b[i][j]<<"  ";
           		cout<<endl;
    	for (i=0;i<3;i++)
      		for (j =0;j<4;j++)
          		cout<<c[i][j]<<"  ";
           		cout<<endl;
    	for (i=0;i<3;i++)
      		for (j =0;j<4;j++)
          		cout<<d[i][j]<<"  ";
           		cout<<endl;
    }
    

    运行结果

    https://cdn.jsdelivr.net/gh/adan-ning/images/202404202106670.png

数组作为函数参数

  1. 数组元素作实参,与单个变量一样。

  2. 无论何时,将数组名作为参数传给函数,实际上只是把数组的地址传给函数。

  3. 数组名作参数,形、实参数都应是数组名,类型要一样,传送的是数组首地址。**因此实参数组的元素个数不应少于形参数组的元素个数。**对形参数组的改变会直接影响到实参数组。

  4. 数组元素作为函数参数

    1. 数组元素作为实参,和变量作为实参一样,是单向的值传递方式

      int  isalp (char  c )             
      { 
        if  (c>='a'&&c<='z'||c>='A'&&c<='Z')
             return(1);
        else  return(0);
      }
      void main( )
      {   int i, num = 0;
          char str[5] = { c, h, 0, n, 5};
      	for( i = 0; i < 5; i++ )
      	     if  ( isalp ( str[i] ) )    num++;
      	 cout << "num=“ << num;	
      }	
      

      https://cdn.jsdelivr.net/gh/adan-ning/images/202404202109411.png

  5. 数组名作为函数参数

    1. 应该在主调函数和被调函数中分别定义数组,不能只在一方定义。
    2. 实参数组和形参数组类型一致。
    3. 形参数组的第一维大小不起任何作用,即使指定,也会被忽略,只是将实参数组的首地址传递给形参数组。
    float  average ( float  array [10] )
    {  int  i; 
        float  aver, sum = array [0];
        for ( i = 1; i < 10; i ++)   
               sum = sum + array [ i ];
        aver = sum /10;
        return   aver;
    }
    void main (  )
    {  float  score [10], aver;
        int  i ;
        for ( i = 0; i < 10; i ++)   
               cin >> score [ i ];
        aver =  average ( score );
        cout << average score << aver ;
    }
    
  6. 数组名作为函数参数

    1. 形参数组可以不指定大小,后面跟一个空的方括号。有时为了在被调函数中处理数组元素的需要,可以另设一个参数,传递需要处理的数组元素的个数。

      float  average ( float  array [ ], int n )
      {  int  i;
          float  aver, sum = array [0];
          for ( i = 1; i <  n; i ++)   
                 sum = sum + array [ i ];
         aver = sum / n;
         return   aver;
      }
      void main (  )
      {  float  score_1 [5] = { 98.5,97,91.5,60,55};
          float  score_2[10] = 
          {67.5,89.5,99,69.5,77,76.5,54,60,95.5,78};    
          cout << average ( score_1,  5) ;
          cout << average ( score_2,  10) ;
      }
      
  7. 数组名作为函数参数

    1. 数组名作为实参,不是把数组的值传递给形参,而是把实参数组的起始地址传递给形参数组,两个数组共占同一段内存单元。
    void sort ( int  b[ ] , int  n )
    {
        . . . 
    }
    void main ( )
    {
        int   a [10];
        . . .
        sort ( a,  10 );
        . . .
    }
    

    https://cdn.jsdelivr.net/gh/adan-ning/images/202404202117178.png

    假设 a 的起始地址为1000,则 b 数组的起始地址也是 1000,显然 a 和 b 同占一段内存单元。 形参数组中各元素的值的变化会使实参数组元素的值同时发生变化。

    在程序设计中有意识地利用这一点改变实参数组元素的值 (如 排序)。

  8. 数组名作为函数参数

    1. 数组名作为实参,不是把数组的值传递给形参,而是把实参数组的起始地址传递给形参数组,两个数组共占同一段内存单元。

    https://cdn.jsdelivr.net/gh/adan-ning/images/202404202120940.png

  9. 主函数中初始化一个矩阵并将每个元素都输出,然后调用子函数,分别计算每一行的元素之和,将和直接存放在每行的第一个元素中,返回主函数之后输出各行元素的和

    例: 使用数组名作为函数参数

    #include <iostream.h>
    void RowSum(int A[ ][4], int nrow)
    {	
     for (int i = 0; i < nrow; i++)
    	{
    		for(int j = 0; j < 4; j++)
    		     A[i][0] += A[i][j];
    	}
    } 
    void main(void)
    {	int Table[3][4] ={{1,2,3,4},{2,3,4,5},{3,4,5,6}};
    	for (int i = 0; i < 3; i++)
    	{	for (int j = 0; j < 4; j++)
    		  cout << Table[i][j] << "   ";
    		cout << endl;  }
    	RowSum(Table,3);
       for (i = 0 ; i < 3 ; i ++)
      {   cout<<Sum of
           row<<i<<is<<Table[i][0]<<endl;
      }
    }
    
    //或:
    #include <iostream.h>
    void RowSum(int A[ ][4], int nrow)
    {	int sum;
    	for (int i = 0; i < nrow; i++)
    	{
    		sum = 0;
    		for(int j = 0; j < 4; j++)
    			sum += A[i][j];
    		cout << "Sum of row " << i<< " is " << sum << endl;
    	}
    } 
    void main(void)
    {	int Table[3][4] =   
            {{1,2,3,4},{2,3,4,5},{3,4,5,6}};
    	for (int i = 0; i < 3; i++)
    	{	for (int j = 0; j < 4; j++)
    			cout << Table[i][j] << "   ";
    		cout << endl;
    	}
    	RowSum(Table,3);
    }
    
    //运行结果:
    //1   2   3   4
    //2   3   4   5
    //3   4   5   6
    //Sum of row 0 is 10
    //Sum of row 1 is 14
    //Sum of row 2 is 18
    

插入排序法( insert sort )

插入排序法是一个简单,但相对比较高效的排序算法。

  1. 设置思想

    插入排序通过把数组中的元素插入到适当的位置来进行排序。

  2. 步骤

    1. 将数组中的头两个元素按排序顺序排列
    2. 把下一个元素(第3个)插入到其对应于已排序元素的排序位置。
    3. 对于数组中的每个元素重复(2)。即把第四个元素插入到适当位置,然后是第5个元素,等。

例:待排序数据元素序列的排序码序列是(18,12,10,12,30,16),写出简单插入排序每一趟执行后的序列状态。

https://cdn.jsdelivr.net/gh/adan-ning/images/202404202126792.png

# include <iostream.h>
void isort(int * a , int size);
void main( )
{
   int array[] = {18,12,10,12,30,16};
   int size = 6;
   for (int i =0 ;i<size;i++)    //原始顺序输出
  cout<<a[i]<<,;
   cout << endl << endl;
   int len = sizeof(array)/sizeof(int);  //元素个数
 isort( array,len);      //调用排序函数
}
void isort( int a[ ] ,int size)    //插入排序
{
  int inserter,index ;
  for (int i =1 ;i <size ; i ++ )  //共执行size-1轮
  {
     inserter = a[i];
     index = i  1;
     while(index>=0 && inserter<a[index])  //寻找插入点
 	{
        a[index+1] = a[index];   //后挪一个位置
       index - - ;
    }
   a[index+1] = inserter ;       //插入
	for (int j = 0 ; j < size ; j++)   //比较一轮后就输出
    {
        cout << a[j] <<,;
        if (j == i )       //已排序与未排序的分界线
   		cout <<|;
     }
      cout << endl ;
   }
}
//运行结果
//18,12,10,12,30,16,
//12,18,|10,12,30,16,
//10,12,18,|12,30,16,
//10,12,12,18,|30,16,
//10,12,12,18,30,|16,
//10,12,12,16,18,30,|

对象数组

  1. 声明:

    类名    数组名[元素个数]
    
  2. 访问方法:

    通过下标访问

    数组名[下标].成员名
    
  3. 对象数组初始化

    1. 数组中每一个元素对象被创建时,系统都会调用类构造函数初始化该对象。

    2. 通过初始化列表赋值。

      例:

      Point A[2]={ Point(1,2), Point(3,4) };
      
    3. 如果没有为数组元素指定显式初始值,数组元素便使用缺省值初始化(调用缺省构造函数)

数组元素所属类的构造函数

  1. 不声明构造函数,则采用缺省构造函数
  2. 各元素对象的初值要求为相同的值时,可以声明具有缺省形参值的构造函数
  3. 各元素对象的初值要求为不同的值时,需要声明带形参(无缺省值)的构造函数
  4. 当数组中每一个对象被删除时,系统都要调用一次析构函数。

例:对象数组应用举例

//Location.h
#if !defined(_LOCATION_H)
#define _LOCATION_H

class Location
{   public:
       Location ; 
       Location(int xx,int yy);
       ~Location ;
     void Move(int x,int y);
       int GetX  {return X;}
       int GetY  {return Y;} 
	private:
       int  X,Y;
};
#endif
//Location.cpp
#include<iostream.h>
#include "Location.h"
Location::Location 
{   X=Y=0;
     cout<<"Default Constructor   called."<<endl;
}
Location::Location(int xx,int yy)
{   X=xx;
     Y=yy;
     cout<< "Constructor called."<<endl;
}
Locatuon::~Location 
{
       cout<<"Destructor called."<<endl;
}
void Location::Move(int x,int y)
{
        X=x;
        Y=y;
}
#include<iostream.h>
#include "Location.h"
int main 
{
       cout<<"Entering main..."<<endl;
       Location A[2];        //定义一个对象数组
       for(int i=0;i<2;i++)
           A[i].Move(i+10,i+20);
       cout<<"Exiting main..."<<endl;
       return 0;
}
//Location A[2]={Location(1,2),Location(3,4)};  //就调有参构造函数 

//运行结果:
//Entering main...
//Default Constructor called.
//Default Constructor called.
//Exiting main...
//Destructor called.
//Destructor called.

指针(关于内存地址)

内存空间的访问方式

  1. 地址编码
    1. 存储单元的地址
    2. 基本内存单元
  2. 从内存单元存取数据的方法
    1. 通过变量名访问
    2. 通过地址访问

https://cdn.jsdelivr.net/gh/adan-ning/images/202404202136069.png

指针变量的概念

  1. 指针和指针变量 的概念

    1. 指针:

      在C和C++中,将地址形象化地称为“指针”。一个变量的地址称为该变量的 “ 指针 ”

    2. 指针变量:

      专门用来存放地址的变量叫做 “ 指针变量 ”

    https://cdn.jsdelivr.net/gh/adan-ning/images/202404202139574.png

指针变量的应用

  1. 指针变量应用的步骤:
    1. 声明指针变量
    2. 给指针变量赋值
    3. 引用指针变量

指针变量的定义与应用

  1. 指针变量的声明

    1. 定义指针变量的一般形式为: 

      数据类型   * 指针变量名
      

      例如:

      int  *pointer_1 ;    float   *pointer_2 ;
      
    2. 指针变量前面的 “ * ”,表示该变量的类型为指针型变量

    3. “数据类型”可以是任意类型,用来指定该指针变量所指向的变量的类型,称之为指针的类型。

    例如: 指针变量 pointer_1 用来存放指向 int 型变量的指针 pointer_2存放指向 float 型变量的指针 。

  2. 指针变量的赋值

    1. 声明一个指针变量,分为全局和局部,**全局未赋值时其中的地址值是0。局部未赋值时其中的地址值是一个随机的数。**因此声明指针变量之后最好先赋值后使用。

      1. 声明时同时初始化:
      数据类型   *指针变量名 = 地址;
      
      1. 使用赋值语句赋值:

        数据类型     *指针变量;
        指针变量 = 地址;
        

指针变量的赋值

  1. “&”是取地址运算符,用来获取一个变量的地址。将获取的变量的地址赋值给指针变量。

  2. 不能把普通非 0 整数赋值给指针变量。

    int *p=(int *)100;       
    int  i;
    int *pointer_1=&i;
    

    https://cdn.jsdelivr.net/gh/adan-ning/images/202404202145816.png

    int    i , * pointer_1;
    pointer_1 = & i ;  
    //将变量 i 的地址存放到指针变量pointer_1 中,因此pointer_1 就“指向”了变量 i 
    
  3. 指针变量的赋值

    1. 使用变量地址赋值时,该变量必须在赋值之前已声明过,且变量类型应与指针类型一致
    2. 可以用一个已赋值的指针变量去赋值给另一 个指针变量
    3. 数组名代表数组的起始地址,可以将数组名表示的地址赋值给指针变量
    //正确
    int    i , * p1, *p2;
    p1 = & i;   
    p2 = p1;
    int array [10] , * p1;
    p1 = array;
    
    //错误,类型不一致
    float   f;
    int   *p;
    p = & f;
    
    #include <iostream>
    using namespace std;
    int main() {
    	int i;			//定义int型数i
    	int *ptr = &i;	//取i的地址赋给ptr
    	i = 10;			//int型数赋初值
    	cout << "i = " << i << endl;//输出int型数的值
    	//输出int型指针所指地址的内容
    	cout << "*ptr = " << *ptr << endl;
    	return 0;
    }
    
    1. 一般情况下,一种类型的指针变量只能存放相同类型的变量的地址

    2. 特殊的void 类型的指针,可以存放任何类型的变量的地址。经过类型强制转换,void 类型的指针可以访问任何类型的数据

      void   *p1;
      int    i,   *p2;
      p1 = & i;
      p2 = ( int * ) p1;
      //可以访问任何类型的数据
      
      #include <iostream>
      using namespace std;
      
      int main() {
      //!	void voidObject;	//错,不能声明void类型的变量
      	void *pv;	//对,可以声明void类型的指针
      	int i = 5;
      	pv = &i;	//void类型指针指向整型变量
      	//void类型指针赋值给int类型指针
      	int *pint = static_cast<int *>(pv);
      	cout << "*pint = " << *pint << endl;
      	return 0;
      } 
      
    3. 指针忘了赋值比整型变量忘了赋值危险得多

      例:

      int count;
      int *iPtr ;
      *iPtr = 58; //指针没有赋值这样非常危险
      

      iPtr当前指向什么地方?该代码能通过编译,但没有赋初值的指针iPtr是一个随机地址

      “*iPtr = 58;”是把58赋到内存中的随机位置,因此很可能已经破坏了另一变量,甚至修改了栈中的函数返回地址,计算机将死机或进入死循环。

    4. 指针变量的引用

      注意:“ * ”出现在声明语句中和执行语句中含义不同。

      1. “ * ”是指针运算符,表示指针所指向的变量。

        int  i , *p;
        p = &i;     
        

        p 是指针变量,* p 就是 p 所指向的变量 i , 即 *p 等价于 变量 i 。

        i = 2;       /* 通过变量名直接访问 */
        *p = 3;    /* *p是 p 所指向的变量,即变量 i ,这是通过指针的间接访问*/ 
        

        https://cdn.jsdelivr.net/gh/adan-ning/images/202404202155806.png

      2. “ * ”出现在声明语句中,表示声明的变量是指针变量。

        //例如:      
        int  *p;
        
      3. “ * ”出现在执行语句中,表示访问指针所指向的变量。

        //例如:  
        *p = 3;
        printf ( %d \n,  *p );
        

      例:

      int i = 26;
      int *P = &i;     //初始化为整型地址
      *P = &i;         //error
      

      **不能将“int P = &i ; ”与“P = &i ; ”混淆

      前者是定义语句,*是指针定义符,C++为P指针分配一个指针空间,并用i 的地址值初始化,后者是执行语句,左右两边类型不匹配。

      *操作符在指针上的两种用途要区分开:定义或声明时,建立一个指针;执行时,间接引用一指针。

      也要注意:

      ”&”出现在声明语句中和执行语句中其含义是不同的;

      例:

      int i , *p;
      int &rf = i;  //表示声明的是引用
      p = &i;    //取地址
      

    例: 指针的声明、赋值与使用

    #include<iostream.h>
    void main 
    {	int *i_pointer;	//声明int型指针i_pointer
    	int i;	                 //声明int型数i
    	i_pointer=&i;  //取i的地址赋给i_pointer
    	i=10;	                //int型数赋初值
    	cout<<Output int i=<<i<<endl; //输出int型数的值
    	cout<<"Output int pointer i="<<*i_pointer<<endl;//输出int型指针所指地址的内容}
    
    1. 指针变量的地址

      指针也是变量,是变量就具有内存地址。所以指针也有地址。

      例:下面的程序输出iCount变量值,以及iPtr和iCount的地址值:

        # include <iostream.h>
        { int iCount = 18 ;
          int * iPtr = &iCount ;
          * iPtr = 58 ;
          cout << iCount << endl ;
          cout << iPrt << endl ;
          cout << &iCount << endl ;   //与iPtr值相同
          cout << *iPtr << endl ;   //与 iCount值相同
          cout << &iPtr << endl ;   //指针本身的地址
      }
      
      //   运行结果:
      //58
      //0x0067fe00
      //0x0067fe00
      //58
      //0x0067fdfc
      

      https://cdn.jsdelivr.net/gh/adan-ning/images/202404211258089.png

    2. 指针与整型数的区别

      1. 指针在使用中必须类型匹配

        例:

        int iCount = 26 ;
        int * iPtr = &iCount ;   //定义语句:*在此处作定义指针变量用,而非间接引用。
        * iPtr = &iCount ;  //error:不能将整型地址转换成整型数
        * iPtr = 50 ; //执行语句:*在此处作间接引用
        
      2. 指针值不是整型数

        赋值语句**“* iPtr = &iCount *; ”在BC中会引起类型转换的错误。 (cannot convert int to int)

      3. 强制转换是合法

        例:允许语句“int * iPtr = (int *) iCount ;”

        但要注意其赋值的意义。该语句表示将变量iCount的值作为一个地址赋给变量 iPtr ,即iCount变量。

        int iCount=50000000000;
        int *p=(int *)iCount;
        cout<<(int *)iCount<<endl;
        cout<<p<<endl;
        cout<<*p<<endl;//ERROR
        

指针与常量(const指针) —指向常量的指针

例:

int a = 1;
int * pi ;
pi = &a ;
* pi = 58 ;

可以看到,一个指针涉及到两个变量,指针本身pi 和指向的变量a 。修改这两个变量的对应操作为“pi = &a ;”和“ pi = 58 ;”。如果不想通过指针间接改变a的值,可以声明指向常量的指针*。

  1. 不能通过指针来改变所指对象的值,但指针本身可以改变,可以指向另外的对象

  2. 在指针定义语句的类型前加const,表示指向的对象是常量

    例:

    int  n1=3;
    int const  n2 = 5;
    const  int  *pn = &n1; 
    pn=&n2;    //正确
    *pn=6;       //错误 
    
  3. 若声明指针常量,则指针本身的值不能被改变

  4. 在指针定义语句的指针名前加const,表示指针本身是常量

    例:

    int   n1 = 3;
    int const   n2 = 5;
    int   *const  pn= &n1; 
    pn = &n2;     //错误
    *pn = 6;        //正确
    

    https://cdn.jsdelivr.net/gh/adan-ning/images/202404211305543.png

  5. void类型指针

    一般情况下,指针的值只能赋给相同类型的指针。但是有一种特殊的void类型指针,可以存储任何类型的对象地址

    例: void类型指针的使用

    void vobject ;  //error,不能声明void类型的变量
    void * pv ;       // ok ,可以声明void类型的指针
    int * pint ; int i ;
    void main( )   // void类型的函数没有返回值
    { pv = &i ;         //void类型指针指向整型变量
     pint = (int *)pv ;  // 类型强制转换
    }       //void类型指针赋值给整型指针
    
  6. 可以定义一个指向常量的指针常量,它必须在定义时进行初始化。

    const int ci = 7 ;
    int ai ;
    
    const int * const cpc = &ci ;  //指向常量的指针常量
    const int * const cpi = &ai ;   //ok
    cpi = &ci ;       // error:指针值不能修改
    *cpi = 39 ;  	// error:不能修改所指向的对象
    ai = 39 ;      //ok
    

指针变量的运算

  1. 指针与整数的加减运算

    1. 指针p 加上或减去 n,其意义是指针当前指向位置的后方或前方第 n 个数据的地址。
    2. 这种运算的结果值取决于指针指向的数据类型。
  2. 指针加一,减一运算

    1. 指向下一个或前一个数据的地址。

      例如:

      y = *px++  
          //<==>  
      y = *(px++) 
      

      ( * 和 ++ 优先级相同,自右向左运算)

    https://cdn.jsdelivr.net/gh/adan-ning/images/202404212222155.png

    https://cdn.jsdelivr.net/gh/adan-ning/images/202404212222572.png

    注意:一般来说,指针的算术运算是和数组的使用相联系的,因为只有在使用数组时,我们才会得到连续分布的可操作内存空间。对于一个独立变量的地址,如果进行算术运算,然后对其结果所指向的地址进行操作有可能会意外破坏该地址中的数据或代码。因此,对指针进行算术运算时, 一定要确保运算结果所指向的地址是程序中分配使用的地址。

  3. 指针变量的关系运算

    1. 关系运算

      1. 指向相同类型数据的指针之间可以进行各种关系运算。

      2. 指向不同数据类型的指针,以及指针与一般整数变量之间的关系运算是无意义的。

      3. 指针可以和零之间进行等于或不等于的关系运算

        例如:

        p == 0 
           // 或 
        p! = 0
        
    2. 赋值运算

      1. 向指针变量赋的值必须是地址常量地址变量不能是普通整数(除0)。但可以赋值为 整数0,表示 空指针(NULL)

        例: 

        int *p ;        //声明一个int型指针p
        p = 0 ;         //将p设置为空指针,不指向任何地址
        

用指针处理数组元素

  1. 声明与赋值

    例:

    int a[10],  *p;
    p = &a[0];    
    //或
    p = a;
    
  2. 通过指针引用数组元素经过上述声明及赋值后:

    1. *p 就是a[0],(p+1) 就是a[1],… ,**(p+i)**就是 a[i]
    2. a[ i ], *(p+i), *(a+i), p[ i ] 都是等效的。
    3. 不能写 a++,因为 a 是数组首地址是常量

    例: 设有一个int型数组a,有10个元素。用三种方法输出各元素:

    1. 使用数组名和下标

      int main 
      {
         int a[10];
         int i;
         for(i=0; i<10; i++)
           cin>>a[i];
         cout<<endl;
         for(i=0; i<10; i++)
           cout<<a[i];
      }
      
    2. 使用数组名和指针运算

      int main 
      {
         int a[10];
         int i;
         for(i=0; i<10; i++)
            cin>>a[i];
         cout<<endl;
         for(i=0; i<10; i++)
           cout<<*(a+i);
      }
      
    3. 使用指针变量

      int main 
      {
         int a[10];
         int *p,i
         for(i=0; i<10; i++)
              cin>>a[i];
         cout<<endl;
         for( p = a; p < (a+10); p++)
              cout<<*p;
      }
      

指针数组

  1. 数组的元素是指针变量

  2. 声明一维指针数组的语法形式

    类型名T *数组名[下标表达式];
    

    例:

    int   *p_i[ 2 ];
    //由 p_i[0], p_i[1] 两个指针组成
    

    例:利用指针数组输出单位矩阵

    void main 
    {	int line1[ ]={1,0,0};   //声明数组,矩阵的第一行
    	int line2[ ]={0,1,0}; //声明数组,矩阵的第二行
    	int line3[ ]={0,0,1};  //声明数组,矩阵的第三行
    
       int  *p_line[3];	      //声明整型指针数组
    	p_line[0]=line1;	   //初始化指针数组元素
    	p_line[1]=line2;
    	p_line[2]=line3;
     //int line1[ ]={1,0,0}; 
    //int line2[ ]={0,1,0}	;
    //int line3[ ]={0,0,1};
    	//输出单位矩阵
       cout<<"Matrix test:"<<endl;
    	for(int i=0;i<3;i++)	 //对指针数组元素循环
    	{
    	    for(int j=0;j<3;j++) //对矩阵每一行循环
    	    {   
                cout << p_line[ i ][ j ] << " ";   
                cout << *( *p_line[ i ] + j ) << ""; //   (p_line[i]+j)表示指向第i行第j 个元素 *(p_line[i]+j)表示(p_line[i]+j)指针所指的变p_line[i][j],与上一句的运行效果一致
            }
    	    cout<<endl;
    	}
    }
    
    //输出结果为:
    //Matrix test:
    //1,0,0
    //0,1,0
    //0,0,1
    

    https://cdn.jsdelivr.net/gh/adan-ning/images/202404212239162.png

    https://cdn.jsdelivr.net/gh/adan-ning/images/202404212240284.png

一维数组

int i ; 
int *p;
int a[10];
p = &i;  

因为: a == &a[0]

所以: p = a; p = &a[0];效果都一样

for(int i=0;i<3;i++)	
{ cout<<p_line[<<i<<]:   << p_line[ i ]<<endl; 
         for(int j=0;j<3;j++)	
	{   cout <<    <<p_line[ i ] + j  << "    ";   }
	    cout<<endl;
	}

https://cdn.jsdelivr.net/gh/adan-ning/images/202404212243179.png

二维数组

例:二维数组举例

#include <iostream.h>
void main 
{	int array2[2][3]={{11,12,13},{21,22,23}};
    for(int i=0;i<2;i++)
    {  cout << *( array2 + i ) <<endl;	//指向i行,相当于array2[i],即第i行的数组
	    for(int j=0;j<3;j++)
        {  cout << *(*( array2 + i ) + j) <<  ;  } //指向第i行的第j个元素

	    cout<<endl;
	}
}

https://cdn.jsdelivr.net/gh/adan-ning/images/202405041532609.png

以指针作为函数参数

  1. 指针变量作为形参
    1. 在函数调用时将实参的地址传递给形参,使实参和形参指针变量指向同一内存单元
    2. 通过在被调用函数中直接处理主调函数中的数据,而将函数的处理结果返回给调用者
  2. 实参是数组名时形参可以是指针变量

在c语言中,以指针作为函数的形参有三个作用:

  1. 使实参与形参指针指向共同的内存空间,以达到参数双向传递的目的,即通过在被调用函数中直接处理主调函数中的数据,而将函数的处理结果返回给调用者
  2. 减少函数调用时数据传递的开销。这一作用在C++中有时可以通过引用实现,有时还是需要使用指针
  3. 通过指向函数的指针传递函数代码的首地址

在程序设计时,如果某个函数中以指针或引用作为形参都可以达到同样目的,则使用引用会使程序的可读性更好些

例:读入三个浮点数,将整数部分和小数部分分别输出

#include <iostream.h>
void  splitfloat ( float x,  int   *intpart,float   *fracpart )
{    //形参 intpart、 fracpart是指针变量
     *intpart = int(x);	 // 取x的整数部分
     *fracpart = x - *intpart;    //取x的小数部分
} 
void main(void)
{
	int   i, n;
	float   x, f;	
  for (i = 0; i < 3; i++)
	{
	  cin >> x;
	  splitfloat ( x, &n, &f);       
           //变量地址做实参
	}
}

例:实参为数组名, 形参为数组名或指针变量

// 形参是数组
float   average ( float   array [ ],  int n )
{  int  i;
    float  aver, sum = array [0];
    for ( i = 1; i <  n; i ++)   
           sum = sum + array [ i ];
    aver = sum / n;
    return   aver;
}
//形参是指针变量
float   average ( float   *p,   int n )
{  int  i;
    float  aver, sum = *p;
    for ( i = 1; i <  n; i ++)   
           sum = sum +  *( p + i) ;
    aver = sum / n;
    return   aver;
}
void main (  )
{  float  score_1 [5] = { 98.5,97,91.5,60,55};
    float  score_2[10] = 
    {67.5,89.5,99,69.5,77,76.5,54,60,95.5,78};    
    
    cout << average ( score_1,  5) ;
    cout << average ( score_2,  10) ;
}

指针型函数

  1. 返回指针的函数称为指针函数
  2. 指针函数不能把在它内部说明的具有局部作用域的数据地址作为返回值
  3. 指针函数的一般定义形式
数据类型 *函数名(参数表)
{
   函数体
}

注意::

  1. 指针函数不能把在它内部说明的具有局部作用域的数据地址作为返回值。可以返回堆地址,可以返回全局或静态变量的地址

例:

# include < iostream.h>
int * getInt(char * str )      //指针函数
{
  int value = 20;
  cout << str << endl;
  return &value ;   
//warning:将局部变量的地址返回是不妥的
}
void somefn(char * str)
{ int a = 40; 
  cout << str << endl ;}
void main( )
  {
    int *pr = getInt("input a value:"); //赋值取自返回的指针值
	cout << *pr << endl;//第一次输出*pr
	somefn("It is uncertain.");  
	cout << *pr << endl;//第二次输出*pr 
}
//运行结果:
//input a value:
//4266377
//It is uncertain.
//4198998

指向函数的指针

  1. 含义:

    1. 每个函数都占用一段内存单元,被分配一个入口地址(起始地址),指向函数地址的指针称为函数的指针

      函数调用: 函数名( 参数表 )

      实质就是: 函数起始地址( 参数表 )

      和数组名代表数组起始地址一样,函数名代表函数入口地址。

      https://cdn.jsdelivr.net/gh/adan-ning/images/202405041546420.png

  2. 指向函数的指针

    1. 用一个指针变量存放函数的起始地址,即指向该函数。通过指针变量就可以访问所指向的函数
  3. 函数指针声明形式

    数据类型 (*函数指针名) (形参表 );

  4. 函数指针在使用之前也要进行赋值,使指针指向一个已经存在的函数代码的起始地址

    函数指针名=函数名

例:函数指针的定义为:

int (*func) (char a , char b );

注意,与指针型函数的区别:

int * func(char a , char b );

定义中, func先与( )结合构成函数的声明,然后再得到其返回类型为整型指针 int *。

例: 函数指针

void print_stuff (float    data_to_ignore);
void print_message (float    list_this_data);
void print_float (float    data_to_print);
void   (*function_pointer) (float);

void   main 
{
    float   pi = 3.14159;
    float   two_pi = 2.0 * pi;
    print_stuff(pi);
   function_pointer = print_stuff;
   function_pointer (pi);
   function_pointer = print_message;
   function_pointer (two_pi);
   function_pointer (13.0);
   function_pointer = print_float;
   function_pointer (pi);
   print_float(pi);
   return 0 ;
}
void print_stuff(float data_to_ignore)
{ cout<<"This is the print stuff unction.\n"; }
void print_message(float list_this_data)
{ cout<<The data to be listed is << list_this_data << endl;  }
void print_float(float data_to_print)
{  cout<<"The data to be printed is”<<data_to_print << endl ;}
  1. 函数指针的内在差别

    1. 省略方括号[ ]的数组名是地址,省略括号( )的函数也是地址,所以可以将省略了( )的函数名作为函数地址赋给函数指针

    2. 函数的差别

      函数类型是指函数的返回类型,这里函数的差别不但是返回类型的差异,还有参数的差异,所以,函数类型的差异不能完全说明函数的差别

      例:下面的代码表示函数和函数指针操作的相互关系:

      int fn1(char x , char y ) ; //两个字符参数和返回整型值的函数
      int * fn2(char x , char y ); //两个字符参数和返回整型指针的函数
      int fn3(int a ) ; //一个整型参数和返回整型值的函数
      int (*fp1) (char a , char b ) ; //两个字符参数和返回整型值的函数指针
      int (*fp2) (int s ) ;  //一个整型参数和返回整型值的函数指针
      fp1 = fn1 ; //ok: fn1函数与指针fp1指向的函数一致
      fp1 = fn2 ; //error: fn2函数与fp1指向的函数不一致
      fp2 = fn3 ;     //ok: 函数参数与返回类型一致,函数名赋给函数指针
      fp2 = fp1 ; //error: 两个指针指向的函数不一致
      fp2 = fn3 (5);     //error: 函数赋给函数指针时,不能加括号
      
    3. 函数指针与其他数据类型的指针尽管都是地址,但在类型上有很大的差别

      例:

      int * ip ;
      void (*fp) ( ) ; //函数指针
      fp = ip ;    //error:不能相互赋值
      ip = fp ;   //error:不能相互赋值
      

指向对象的指针

  1. 对象指针的一般概念

    1. 声明形式

      类名 *对象指针名;

    2. Point   p1(5,10);
      Piont   *ptr;
      ptr = & p1;
      
    3. 通过指针访问对象成员

      对象指针名 ->成员名

    例: 对象指针应用举例

    #include< iostream.h>
    class   Point
    { 
      public:
          Point (int xx, int yy){ X = xx; Y = yy; }
          int GetX( )  { return  X; }
          int GetY( )   { return  Y; }
     private:
           int     X,  Y;   
     }; 
    void  main 
    {
         Point   p1(5,10);
         Point   *ptr;
         ptr=&p1;
    	cout << ptr -> GetX ( ) <<endl;
    	cout << p1.GetX ( ) <<endl;
    }
    
    //p1-> GetX ( )  错误
    //ptr->X = 15; 错误
    
  2. this指针

    1. this 指针是一个隐含于每一个类的成员

      函数中的特殊指针(包括构造函数和析构函数),它用于指向正在被成员函数操作的对象

    2. this 指针就明确地指出了成员函数当前 所操作的数据所属的对象

    3. 实际过程

      当通过一个对象调用成员函数时,系统先 将该对象的地址赋给this指针,然后调用成员 函数,成员函数对对象的数据成员进行操作时, 就隐含使用了this指针

    4. this 是一个指针变量,因此在成员函数中,可以使用*this来标识正在调用该函数的对象

    https://cdn.jsdelivr.net/gh/adan-ning/images/202405041559787.png

  3. 指向类的非静态成员的指针

    1. 类的成员自身也是一些变量、函数或者对象等,也可以直接将它们的地址存放到一个指针变量中,这样,就可以使指针直接指向对象的成员,进而可以通过这些指针访问对象成员

    2. 声明指针的语句形式

      类型说明符 类名::*指针名 
      类型说明符 (类名::*指针名)(参数表)
      
    3. 注意:通过指向成员的指针也只能访问到公有成员

    4. 对数据成员指针赋值的一般语法形式

      指针名 = &类名::数据成员名

    5. 说明:

      1. 上式只是说明了被赋值的成员指针是专门 用于指向哪个数据成员的,同时在指针中存放该数据成员在类中的相对位置
      2. 当然通过这样的指针现在并不能访问什么
      3. 由于类是通过对象而实例化的,在声明类的对象时才会为具体的对象分配内存空间,这时只要将对象在内存中的起始地址与成员指针存放的相对偏移结合起来就可以访问到对象的数据成员了。
    6. 访问数据成员的两种语法形式

      对象名. *类成员指针名
      对象指针名-> *类成员指针名
      
    7. 成员函数指针在声明之后要用以下形式的语句对其赋值:

      指针名 = 类名::函数成员名
      
    8. 利用指针调用成员函数的语句形式

      (对象名. *类成员指针名)(参数表)
      (对象指针名-> *类成员指针名)(参数表)
      

    例: 访问对象的公有成员函数的不同方式

    #include<iostream>
    using namespace std;
    class   Point
    { 
       public:
          Point (int xx, int yy){ X = xx; Y = yy; }
          int GetX( ) { return  X; }
          int GetY( ) { return  Y; }
     private:
           int     X,  Y;
    }; 
    void main( )               //主函数
    { point A(4,5) ;          //声明对象A
       point *p1 = &A ;  //声明对象指针并初始化
       int  (point::*p_GetX)( ) = point::GetX; //声明成员函数指针并初始化
      cout<<(A.*p_GetX)( ) << endl;  //使用成员函数指针访问成员函数
      cout<<(p1->GetX)( ) << endl;  //使用对象指针访问成员函数
      cout<< A. GetX( ) << endl;     } //使用对象名访问成员函数
    
  4. 指向类的静态成员的指针

    1. 类的静态成员可以用普通的指针来指向和访问

      例:通过指针访问类的静态数据成员

      #include<iostream>
      using namespace std;
      class   point
      { 
      public:
            point (int xx = 0, int yy = 0) //构造函数
                     { X = xx; Y = yy; countp++; }
            point (point &p);    //拷贝构造函数
            int GetX( ) { return  X; }
            int GetY( ) { return  Y; } 
       	  stacit int countp ;  //静态数据成员引用性说明
      private:
             int  X,  Y;
      }; 
      point::point(point &p)
       {
         x = p.x ;
         y = p.y ;
         countp + + ;
       }
      int point::countp = 0 ;  //静态数据成员定义性说明
      void main( )       //主函数实现
      {  int  *count = &point::countp ; /声明一个int型指针,指向类的静态成员
         point A(4,5) ;   //声明对象A
         cout<<point A,<<A.GetX( )<<,<< A.GetY( ) ;
        cout<<object id=<<*count<<endl ; //直接通过指针访问静态数据成员
        point B(A);      //声明对象B
        cout<<point B,<<B.GetX( )<<,<< B.GetY( ) ;
        cout<<object id = <<*count<<endl ; //直接通过指针访问静态数据成员
      }
      

      例: 通过指针访问类的静态函数成员

      #include<iostream>
      using namespace std;
      class   point
      { 
      public:
          point (int xx = 0, int yy = 0) //构造函数
                  { X = xx; Y = yy; countp++; }
          point (point &p);    //拷贝构造函数
          int GetX( ) { return  X; }
          int GetY( ) { return  Y; } 
          static void Getc( )      //静态函数成员
                {cout<<object id = <<countp<<endl ; }
      private:
           int  X,  Y;
       	 static int countp ; //静态数据成员引用性说明
      }; 
        	  point::point(point &p)
       		{  x = p.x ;
           	   y = p.y ;
           	   countp + + ;
       }
      int point::countp = 0 ;  //静态数据成员定义性说明,初始化,使用类名限定 
      void main( )       //主函数实现
      { void (*gc)( ) = Point::GetC ; //声明一个指向函数的指针,指向类的静态成员函数
         point A(4,5) ;   //声明对象A
      cout<<point A,<<A.GetX( )<<,<< A.GetY( ) ;
         gc( ) ; 
       //输出对象序号,直接通过指针访问静态成员函数
        point B(A);      //声明对象B
      cout<<point B,<<B.GetX( )<<,<< B.GetY( ) ;
       gc( ) ; 
       //输出对象序号,直接通过指针访问静态成员函数
      }
      

      https://cdn.jsdelivr.net/gh/adan-ning/images/202405041608782.png