亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

C/C++中指針和引用之相關問題深入研究

 更新時間:2013年10月08日 09:09:17   作者:  
從內存分配上看,程序為指針變量分配內存區(qū)域,而不為引用分配內存區(qū)域,因為引用聲明時必須初始化,從而指向一個已經存在的對象。引用不能指向空值

一、基本知識
指針和引用的聲明方式:
聲明指針: char* pc;
聲明引用: char c = 'A'
   char& rc = c;

它們的區(qū)別:
①從現象上看,指針在運行時可以改變其所指向的值,而引用一旦和某個對象綁定后就不再改變。這句話可以理解為:指針可以被重新賦值以指向另一個不同的對象。但是引用則總是指向在初始化時被指定的對象,以后不能改變,但是指定的對象其內容可以改變。

②從內存分配上看,程序為指針變量分配內存區(qū)域,而不為引用分配內存區(qū)域,因為引用聲明時必須初始化,從而指向一個已經存在的對象。引用不能指向空值。

③從編譯上看,程序在編譯時分別將指針和引用添加到符號表上,符號表上記錄的是變量名及變量所對應地址。指針變量在符號表上對應的地址值為指針變量的地址值,而引用在符號表上對應的地址值為引用對象的地址值。符號表生成后就不會再改,因此指針可以改變指向的對象(指針變量中的值可以改),而引用對象不能改。這是使用指針不安全而使用引用安全的主要原因。從某種意義上來說引用可以被認為是不能改變的指針。

④不存在指向空值的引用這個事實意味著使用引用的代碼效率比使用指針的要高。因為在使用引用之前不需要測試它的合法性。相反,指針則應該總是被測試,防止其為空。

⑤理論上,對于指針的級數沒有限制,但是引用只能是一級。

如下:

復制代碼 代碼如下:

  int** p1; // 合法。指向指針的指針
  int*& p2; // 合法。指向指針的引用
  int&* p3; // 非法。指向引用的指針是非法的
  int&& p4; // 非法。指向引用的引用是非法的

注意上述讀法是從左到右。

程序1:
復制代碼 代碼如下:

#include "stdio.h"
int main(void)
{
  // 聲明一個char型指針pc,且讓它指向空值
  char* pc = 0;
  char a = 'a';
  // 聲明一個引用rc,且讓它引用變量a
  char& rc = a;
  printf("%d, %c\n", pc, rc);

  char *pc2;
  // 聲明一個指針,但可以不初始化
  pc2 = pc;

  // char& rc2;
  // 上面語句編譯時,會產生如下錯誤:
  // error C2530: 'rc2' : references must be initialized
  // 即,應用必須初始化
  // rc = *pc;
  // 上面語句編譯不會有問題,但運行時,會報如下錯誤:
  // "0x00401057"指令引用的"0x00000000"內存。該內存不能為"read"
  // 說明引用在任何情況下,都不能指向空值

  return 0;
}

程序2:
復制代碼 代碼如下:

#include <iostream>
#include <string>
using namespace std;
int main(void)
{
  string s1("Hello");
  string s2("World");
  // printf("%s\n", s1); 不能用printf輸出s1,而應該用cout

  cout << "s1的地址 = "<< &s1 << endl;// &s1 = 0012FF64
  cout << "s2的地址 = "<< &s2 << endl;// &s2 = 0012FF54

  string& rs = s1;   // 1. 定義一個引用rs,rs引用s1
  cout << "引用rs的地址 = " << &rs << endl;  // &rs = 0012FF64

  string* ps = &s1; //定義一個指針ps, ps指向s1
  cout << "指針ps的地址 = " << ps << endl;// ps = 0012FF64

  cout << rs << ", " << *ps << endl;  // Hello, Hello
  // 如果沒有#include <string>,上面的語句在編譯的時候,會出現如下錯誤:
  // error C2679: binary '<<' : no operator defined which takes a right-
  // hand operand of type 'class std::basic_string<char,struct
  // std::char_traits<char>,class std::allocator<char> >'
  // (or there is no acceptable  conversion)

  rs = s2;  // 2. rs仍舊引用s1, 但是s1現在的值是"World"
  ps = &s2;   // ps現在指向s2

  cout << "引用rs的地址 = " << &rs << endl;  // &rs = 0012FF64 未改變
  cout << "引用rs的值 = " << rs << endl;   // rs = "World" 已改變

  cout << "指針ps的地址 = " << ps << endl;// ps = 0012FF54  已改變
  cout << "指針ps所指地址的內容 = " << *ps << endl;  // *ps = World已改變

  cout << "s1的地址 = "<< &s1 << endl;// 3. &s1 = 0012FF64 未改變
  cout << "s1的值 = " << s1 << endl; // 4. s1 = World  已改變

  return 0;
}

可以認為:
引用就是變量的別名,在引用初始化的時候就已經確定,以后不能再改變。見程序2的粗體字語句。第1句,聲明了rs引用s1,s1的值為”Hello”,從這以后,rs實際上就相當于變量s1了,或者從更本質的意義上來說,rs的地址就是初始化時s1的地址了,以后都不會再改變。這應該比較好理解,比如我們在程序中定義了一個變量a,不管我們如何給a賦值,但它的地址是不會改變的;

第2句,rs仍舊指向初始化時s1的地址,但此處的賦值就相當于重新給s1賦值,因此我們從第3句和第4句可以看到,s1的地址并沒有發(fā)生變化,但是其值已經發(fā)生了變化。

二、作為參數傳遞
利用引用的這個特性,可以用它作為函數的傳出參數。如程序3:

復制代碼 代碼如下:

#include <iostream>
#include <string>
using namespace std;
int newEvaluation(string& aStr)
{
  string bStr("Hello,");
  aStr = bStr + aStr;

  return 0;
}

int main(void)
{
  string aStr("Patrick!");
  newEvaluation(aStr);
  std::cout << aStr << endl; // 輸出結果:"Hello, Patrick!"

  return 0;
}

而一般變量,則不能從函數內部傳值出來,比如程序4:
復制代碼 代碼如下:

#include <iostream>
#include <string>
using namespace std;

int newEvaluation(string aStr)
{
  string bStr("Hello,");
  aStr = bStr + aStr;

  return 0;
}

int main(void)
{
  string aStr("Patrick!");
  newEvaluation(aStr);
  std::cout << aStr << endl; // 輸出結果:"Patrick!",aStr的值沒有變化

  return 0;
}

當然程序3引用傳遞的方式也可以寫成指針傳遞的方式,如程序5:
復制代碼 代碼如下:

#include <iostream>
#include <string>
using namespace std;

int newEvaluation(string* const aStr)
{
  string bStr("Hello,");
  *aStr = bStr + *aStr;

  return 0;
}

int main(void)
{
  string aStr("Patrick!");
  newEvaluation(&aStr);
  std::cout << aStr << endl; // 輸出結果:"Hello, Patrick!"

  return 0;
}

注意程序中的陷井,如程序6:
復制代碼 代碼如下:

#include <iostream.h>
int *pPointer;
void SomeFunction()
{
  int nNumber;
  nNumber = 25;
  //讓指針指向nNumber
  pPointer = &nNumber;
}

void main()
{
  SomeFunction();//為pPointer賦值
  //為什么這里失敗了?為什么沒有得到25
  cout << "Value of *pPointer: " << *pPointer << endl;
}

這段程序先調用了SomeFunction函數,創(chuàng)建了個叫nNumber的變量,接著讓指針pPointer指向了它。可是問題出在哪兒呢?當函數結束后,nNumber被刪掉了,因為這一個局部變量。局部變量在定義它的函數執(zhí)行完后都會被系統(tǒng)自動刪掉。也就是說當SomeFunction 函數返回主函數main()時,這個變量已經被刪掉,但pPointer還指著變量曾經用過的但現在已不屬于這個程序的區(qū)域。

盡管在SomeFunction中使用所謂的動態(tài)分配內存。程序7中也存在陷井:
復制代碼 代碼如下:

#include <iostream.h>
int *pPointer;

void SomeFunction()
{
int intNumber = 25;
// 讓指針指向一個新的整型
pPointer = new int;
pPointer = &intNumber;
}

void main()
{
SomeFunction();   // 為pPointer賦值
cout<< "Value of *pPointer: " << *pPointer << endl;
delete pPointer;
}

原因也如上面所言,intNumber的作用范圍僅限于SomeFunction中,離開了SomeFunction,那么intNumber就不存在了,那么&intNumber即intNumber的地址就變得沒有意義了,因此,該地址所指向的值是不確定的。如果改為下面的程序就不會有問題了。

程序8:

復制代碼 代碼如下:

#include <iostream.h>
int *pPointer;

void SomeFunction()
{
int intNumber = 25;
// 讓指針指向一個新的整型
pPointer = new int(intNumber);
}

void main()
{
SomeFunction();   // 為pPointer賦值
cout<< "Value of *pPointer: " << *pPointer << endl;
delete pPointer;
}

三、指針的指針
前面說到,指針是沒有級數限制的。
程序9:
復制代碼 代碼如下:

#include<stdio.h>
#include<stdlib.h>

void main(void)
{
int i, j;
int a[10], b[3][4], *p1, *p2, **p3;  
for(i = 0; i < 10; i++)
   scanf("%d", &a[i]);  

for(i = 0; i < 3; i++)
   for(j = 0; j < 4; j++)
   scanf("%d", &b[i][j]);

p1 = a;
p3 = &p1;
for(i = 0; i < 10; i++)
   printf("%4d", *(*p3+i));
printf("\n");

for(p1 = a; p1 - a < 10; p1++)
{
   p3 = &p1;
   printf("%4d", **p3);
}
printf("\n");

for(i = 0; i < 3; i++)
{
   p2 = b[i];
   p3 = &p2;
   for(j = 0; j < 4; j++)
   printf("%4d",*(*p3+j));
   printf("\n");
}

for(i = 0; i < 3; i++)
{
   p2 = b[i];
   for(p2 = b[i]; p2-b[i] < 4; p2++)
   {
   p3 = &p2;
   printf("%4d", **p3);
   }
   printf("\n");
}
}

輸出的結果:
1   2   3   4   5   6   7   8   9   10
1   2   3   4   5   6   7   8   9   10
11  12  13  14
15  16  17  18
19  20  21  22
11  12  13  14
15  16  17  18
19  20  21  22

四、函數指針和函數引用
函數指針是C++最大的優(yōu)點之一。和使用普通指針相比,高級程序員只要有可能都更愿意使用引用,因為引用更容易處理一些。然而,當處理函數時,函數引用對比函數指針就未必有這個優(yōu)勢了。現有的代碼很少使用函數引用。下面將向介紹如何函數指針、如何使用函數引用以及分別在什么情況下使用它們。

① 函數指針的例子

復制代碼 代碼如下:

#include <iostream>
void print(int i)
{
std::cout << i << std::endl;
}

void multiply(int& nDest, int nBy)
{
nDest *= nBy;
}

void print_something()
{
std::cout << "something" << std::endl;
}

int sayHello()
{
std::cout << "Hello, World!" << std::endl;
return 10;
}

int main()
{
void (*pFunction_1)(int);
pFunction_1 = &print;
pFunction_1(1);
// 輸出結果為1

void (*pFunction_2)(int&, int) = &multiply;
int i = 1;
pFunction_2(i, 10);
std::cout << "i = " << i << std::endl;
// 輸出結果為10

void (*pFunction_3)();
pFunction_3 = &print_something;
pFunction_3();
// 輸出結果為something

int (*pFunction_4)();
pFunction_4 = &sayHello;
int a = pFunction_4();
// 輸出結果為Hello, World!
std::cout << a << std::endl;
// 輸出結果為10

return 0;
}

② 函數引用的例子
復制代碼 代碼如下:

#include <iostream>
void print(int i)
{
std::cout << i << std::endl;
}

void print2(int i)
{
std::cout << i << std::endl;
}

void multiply(int& nDest, int nBy)
{
nDest *= nBy;
}

void print_something()
{
std::cout << "something" << std::endl;
}

int sayHello()
{
std::cout << "Hello, World!" << std::endl;
return 10;
}

 
int main()

// void (&rFunction_1)(int);
// 錯誤:未初始化引用!引用必須初始化

void (&rFunction_2)(int) = print;
rFunction_2(1);
// 輸出1

rFunction_2 = print2;
rFunction_2(2);
// 輸出2

void (&rFunction_3)(int&, int) = multiply;
int i = 1;
rFunction_3(i, 10);
std::cout << i << std::endl;
// 輸出10

void (&rFunction_4)() = print_something;
rFunction_4();
// 輸出something

int (&rFunction_5)();
rFunction_5 = sayHello;
int a = rFunction_5();   // 輸出Hello, World!
std::cout << a << std::endl;
// 輸出10

return 0;
}

③ 函數指針和函數引用作為函數參數
復制代碼 代碼如下:

#include <iostream>

void print(int i)
{
std::cout << i << std::endl;
}

void print2(int i)
{
std::cout << i * 2 << std::endl;
}

void printSomething()
{
std::cout << "Something" << std::endl;
}

void sayHello()
{
std::cout << "Hello, World!" << std::endl;
}

void call_p_func(void (*func)(int))
{
func(1);
func(2);
func(3);
}

void call_r_func(void (&func)(int))
{
func(1);
func(2);
func(3);
}

void call_p_function(void (*func)())
{
func();
}

int main()
{
std::cout << "函數指針作為參數" << std::endl;
call_p_func(&print);
call_p_func(&print2);
call_p_function(&printSomething);
call_p_function(&sayHello);
call_p_function(sayHello);
// 上面兩句對于某些編譯器來說是一樣的,但是推薦使用前者的寫法,
// 這樣可以是程序的可讀性更好一些

std::cout << "函數引用作為參數" << std::endl;
call_r_func(print);
call_r_func(print2);

return 0;
}

總結:
函數指針的聲明使用方式:
<想要指向的函數之返回類型>(*函數指針的名稱)<想要指向的函數之參數類型…>
如要想聲明一個函數指針指向以下函數:
復制代碼 代碼如下:

void print(int i)
{
std::cout << i << std::endl;
}

那么就可以如下操作:
void (*pFunction)(int);
然后如下用函數的地址給pFunction賦值:
pFunction = &print;
在然后,pFunction就可以和函數print一樣使用了,比如,
pFunction(1);
等等。

函數引用的聲明和使用方式:
<欲引用的函數之返回類型>(&函數引用的名稱)<欲引用的函數之參數類型…>=<欲引用的函數的名稱>,至所以如此,是引用在聲明的時候必須初始化,引用不能指向空值。
如要想聲明一個函數引用指向以下函數:
復制代碼 代碼如下:

void print(int i)
{
std::cout << i << std::endl;
}

那么就可以如下操作:
void (&rFunction)(int)=print;
在然后,rFunction就可以和函數print一樣使用了,比如,
rFunction(1);
等等。

五、const修飾指針和引用
大致而言,const修飾指針和引用分三種情況,即const修飾指針、const修飾引用和const修飾指針的引用。下面分別討論之。

① const修飾指針
const修飾指針又分為三種情況,即const修飾指針本身、const修飾指針所指的變量(或對象)以及const修飾指針本身和指針所指的變量(或對象)。

a. const修飾指針本身
在這種情況下,指針本身是常量,不能改變,任何修改指針本身的行為都是非法的,例如:
double pi = 3.1416;
double* const PI = &pi;

double alpha = 3.14;
PI = &alpha;   // 錯誤。因為指針PI是常量,不能再被改變。
*PI = alpha;   // OK。雖然指針PI不能被改變,但指針所指的變量或者對象可變。

b. const修飾指針指向的變量(或對象)
在這種情況下,指針本身可以改變,但const所修飾的指針所指向的對象不能被改變,例如:
double pi = 3.1416;
const double* PI = &pi;

double alpha = 3.14;
*PI = alpha;// 錯誤。因為PI所指向的內容是常量,因此*PI不能被改變。
PI = &alpha;// OK。雖然指針所指的內容不能被改變,但指針PI本身可改變。從而通過這種方式改變*PI。

c. const修飾指針本身和指針所指向的變量(或對象)
在這種情況下,指針本身和指針指向的變量(或對象)均不能被改變,例如:
double pi = 3.146;
const double* const PI = &pi;
//double const* const PI = &pi;
cout << "PI = " << PI << endl;
cout << "*PI = " << *PI << endl;

double alpha = 3.14;
//*PI = alpha; // 錯誤。因為PI所指向的內容是常量,因此*PI不能被改變。
//PI = &alpha; // 錯誤。因為指針PI是常量,不能再被改變。

② const修飾引用
const修飾引用沒有指針修飾指針那么復雜,只有一種形式。引用本身不能被改變,但所指向的對象是可以被改變的,見上面“一、基本知識”。
double pi = 3.1416;
//const double& PI = pi;
double const& PI = pi;  //和上面一句是等價的
//double& const PI = pi;//有問題。很多編譯器會產生warning
cout << PI << endl;

③ const修飾指針引用
我們用例子來說明。
double pi = 3.14;
const double* pPI = &pi;
//const double*& rPI = &pi; //錯誤。不能將double* 轉換成const double *&
const double*& rPI = pPI;   //OK。聲明指針引用的正確方法

說明:const double*& rPI = &pi; 為什么會出現錯誤呢?我們知道,引用是被引用對象的別名,正因為如此,由于rPI是pPI的別名,因此rPI和pPI的類型必須完全一致。從上面的代碼段我們可以看到,rPI的類型是const double*,而&pi的類型是double*,因此這句程序是錯誤的。

下面這段代碼和 ① 中的b中的情形對應(即內容不可變,指針可變):
double pi = 3.1416;
double api = 3.14;
const double* pPI = &pi;
const double* pAPI = &api;
const double*& rPI = pPI;
const double*& rAPI = pPI;

*rAPI = api; // 錯誤。指針所指向的值不能被直接改變
rAPI = pAPI;   // OK。指針本身可以被改變

指針引用的用法還有其它的情形,由于罕用,故此不談及。

相關文章

  • Qt實現TCP客戶端和服務器通訊程序

    Qt實現TCP客戶端和服務器通訊程序

    這篇文章主要為大家詳細介紹了Qt實現TCP客戶端和服務器通訊程序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • 關于STL中l(wèi)ist容器的一些總結

    關于STL中l(wèi)ist容器的一些總結

    list就是數據結構中的雙向鏈表(根據sgi stl源代碼),因此它的內存空間是不連續(xù)的,通過指針來進行數據的訪問,這個特點使得它的隨即存取變的非常沒有效率,因此它沒有提供[]操作符的重載
    2013-09-09
  • C++之CNoTrackObject類和new delete操作符的重載實例

    C++之CNoTrackObject類和new delete操作符的重載實例

    這篇文章主要介紹了C++之CNoTrackObject類和new delete操作符的重載實例,是C++程序設計中比較重要的概念,需要的朋友可以參考下
    2014-10-10
  • C語言如何在字符數組中插入一個字符

    C語言如何在字符數組中插入一個字符

    這篇文章主要介紹了C語言如何在字符數組中插入一個字符,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • C++ OpenCV學習之圖像像素值統(tǒng)計

    C++ OpenCV學習之圖像像素值統(tǒng)計

    在圖像分析的時候,我們經常需要對單通道圖像的像素進行統(tǒng)計。本文將主要介紹利用C++ OpenCV實現的圖像像素值統(tǒng)計的幾種方法,需要的可以參考一下
    2022-01-01
  • C與C++ 無參函數的區(qū)別解析

    C與C++ 無參函數的區(qū)別解析

    在《C++ 編程思想》:“關于無參函數聲明,C與C++有很大的差別。在C語言中,聲明int fun1(),意味著一個可以有任意數目和類型的函數;而在C++中,指的卻是一個沒有參數的函數”
    2013-07-07
  • C++日期類(Date)實現的示例代碼

    C++日期類(Date)實現的示例代碼

    這篇文章主要為大家詳細介紹了如何利用C++語言實現日期類(Date),可以實現確定某年某月有多少天、打印日期等功能,感興趣的可以了解一下
    2022-07-07
  • C語言零基礎徹底掌握預處理上篇

    C語言零基礎徹底掌握預處理上篇

    在C語言的程序中包括各種以符號#開頭的編譯指令,這些指令稱為預處理命令。預處理命令屬于C語言編譯器,而不是C語言的組成部分,通過預處理命令可擴展C語言程序設計的環(huán)境
    2022-08-08
  • C++實現LeetCode(108.將有序數組轉為二叉搜索樹)

    C++實現LeetCode(108.將有序數組轉為二叉搜索樹)

    這篇文章主要介紹了C++實現LeetCode(108.將有序數組轉為二叉搜索樹),本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下
    2021-07-07
  • C語言動態(tài)內存管理的原理及實現方法

    C語言動態(tài)內存管理的原理及實現方法

    C語言動態(tài)內存管理的原理是通過 malloc() 函數申請一塊連續(xù)的內存空間,并返回其地址,通過 free() 函數釋放該內存空間。實現方法是通過在程序運行時動態(tài)地管理內存,即在需要內存時申請,不需要時釋放,避免了靜態(tài)內存分配的浪費和不足
    2023-04-04

最新評論