指針(2)
函數的指針
函數在編譯時被分配給一個入口地址。這個入口地址稱爲函數的地址,也是函數的指針。
函數名代表函數的入口地址
int max(int x,int y){
return x>y?x:y;
}
int min(int x,int y){
return x<y?x:y;
}
int (*p)(int,int);
p = max;
p = min;
-
函數名max代表函數在內存中的入口地址,是一個常量,不可被賦值。
-
指向函數的指針變量 p 可以指向不同的同種類型的函數,但不可作加減運算。
-
通過指針來調用函數
x = (*p)(1,2);
x = p(1,2);
第二種格式看上去和函數調用無異。傾向於使用第一種格式,因爲它明確指出是通過指針而非函數名來調用函數的。
函數指針做函數參數時
<type> f(<type> (*p)(<type1,type2,……>)
{……}
調用時:
f(f1);
切不可寫成:
f(f1(……))
要注意函數名就代表函數的入口地址。
demo
void myExp()
{
int x;
while (1) {
cin>>x;
if(x<0){
return;
}
cout<<exp(x)<<endl;
}
}
void linear()
{
int x;
while (1) {
cin>>x;
if(x<0){
return;
}
cout<<2*x<<endl;
}
}
void square()
{
int x;
while (1) {
cin>>x;
if(x<0){
return;
}
cout<<x*x<<endl;
}
}
void display(void (*funtion)())
{
cout<<"調用此函數"<<endl;
(*funtion)();
}
int main(){
display(myExp);
void (*p)();
p = linear;
display(p);
p = square;
display(p);
return 0;
}
調用qsort庫函數,將一個unsigned int數組按照個位數從小到大進行排序。比如 8, 23, 15三個數,按個位數從小到大排序,就應該是 23, 15, 8
int cmp(const void* e1,const void* e2){
unsigned int *x1 =(unsigned int*)e1;
unsigned int *x2 =(unsigned int*)e2;
return (*x1)%10 - (*x2)%10;
}
unsigned int a[] = {100001,85,2,997,19};
qsort(a,NUM,sizeof(unsigned int), cmp);
返回指針的函數
被調函數返回一個指針時,函數的類型稱爲指針類型。
類型標識符 *函數名(參數表)
int *max(int*,int*);
int *max(int *a,int *b)
{
int *pt;
if(*a>*b)
pt = a;
else
pt = b;
return pt;
}
int main(){
int a,b,*p;
cin>>a>>b;
p = max(&a, &b);
cout<<*p<<endl;
return 0;
}
C++ 不支持在函數外返回局部變量的地址,除非定義局部變量爲 static 變量。
錯誤示範:
char* getMemory()
{
char p[100];
return p;
}
int main(){
int x;
cout<<&x<<endl;
char *str = getMemory();
delete []str;
return 0;
}
static
修飾
char* getMemory()
{
static char p[] ="safd";
return p;
}
int main(){
char *str = getMemory();
cout<<str<<endl;
return 0;
}
返回指向堆內存的指針:
char* getMemory()
{
char*p = new char[100];
cout<<static_cast<void*>(p)<<endl;
return p;
}
int main(){
int x;
cout<<&x<<endl;
char *str = getMemory();
delete []str;
return 0;
}
指針數組
指針數組是一個數組,其元素均爲指針類型的數據。也就是說,指針數組中的每一個元素可以放地址。
<類型> *數組名[數組長度];
int *point_array[10];
數組名也就是數組的指針
注意[]
比*
優先級高,p爲數組名,有四個元素,每個元素可以放一個int型數據的地址
int (*p)[4];
p爲指向有四個int型元素的一維數組的指針。
int *p[10];
*p[i] 相當於 *(*(p+i))
int main(){
int a[9]={1,2,3,4,5,6,7,8,9};
int *pa[3];
pa[0] = a;
pa[1] = a+3;
pa[2] = &a[6];
cout<<pa[0][0]<<endl;
cout<<pa[2][1]<<endl;
cout<< *(*(pa+2)+1)<<endl;
return 0;
}
1
8
8
字符指針數組
經常用字符指針數組,數組中的元素指向字符串首地址,這樣比字符數組節省空間。
如何存儲一組字符串,二維字符數組是一個可行的方法,但是該方法有一個致命問題,二維數組的列數是需要明確給出的,每一行的列數相同,然而字符串有長有短,這樣勢必造成較大的空間浪費,若是需要交換字符串順序,則更是有很大的計算代價。
用指針數組則可以較好規避上面的問題,將指針數組中每個元素賦值爲一個字符串的首地址,就完成了一組字符串的表示,那麼具體有哪些優點呢?
1)各字符串在數組內的位置調整將更加方便。這時只需要改變數組內各指針的指向,而無需實際調整字符串在內存中的存放位置。
2)相對於二維數組來說,這樣的組織方式允許不等長的字符串能夠以一種相對規整的方式組織在一起,看上去的效果就好像數組中的每個元素就是一個字符串一樣,儘管每個元素只是指向某字符串的指針。
3)指向字符串的指針數組的初始化更簡單,各個字符串都是可以分別定義的,只要讓數組中的指針指向各字符串即可。
int main(){
const char *str[] = {"China","Japan","America"};
cout<<str[1][4]<<endl;
cout<<str[2]<<endl;
cout<<*str[2]<<endl;
cout<<str<<endl;
cout<<*str<<endl;
cout<<**str<<endl;
return 0;
}
America
A
0x7ffeefbff610
China
C
- 字符串的排序(這裏就用選擇排序)
void printStrings(const char *name[],int n)
{
for(int i=0;i<n;i++)
{
cout<<name[i]<<endl;
}
}
void sortStrings(const char *name[],int n)
{
for(int i=0;i<n-1;i++)
{
int mmin = i;
for(int j=i+1;j<n;j++)
{
if(strcmp(name[mmin], name[j])>0)
{
mmin = j;
}
}
const char *t = name[i];name[i] = name[mmin]; name[mmin] = t;
}
}
int main(){
const char *name[] = {"Follow me", "Basic", "Great Wall",
"FORTRAN", "Computer design"};
sortStrings(name, 5);
printStrings(name, 5);
return 0;
}
指針的指針
指針也有地址,可以再引用一個指針變量指向它。
int x,*px,**ppx;
px = &x;
ppx = &px;
在上面的所講述的 指針的數組中,數組名或者說指針數組的地址,也就是指針數組的指針其實就是指向指針的指針。
例如:
int a,b,c;
int *pa[] = {&a,&b,&c};
int **pp = pa;
int main(){
int a[] = {1,2,3,4,5};
int *pa[] = {&a[0],&a[1],&a[2],&a[3],&a[4]};
int **pp = pa;
for(int i=0;i<5;i++){
cout<<**pp++<<endl;
}
return 0;
}
main
函數的形參
int main(int argc,char* argv[]){
……
}
argc
爲命令行中參數的個數(包括文件名);
argv
爲指向命令行中參數(字符串)的指針數組。
- 指針的空值
指針變量可以有空值,即指針變量不指向任何地址。
int *p;
p = 0;
#define NULL 0
int *p = NULL;
- 指針的運算
- 指針可以進行相減,不可相加;
但相減的前提是兩個指針必須指向同一數組。
相減結果爲相距的數組元素個數。
- 指針可以進行相減,不可相加;
int a[10];
int *p1 = a,*p2 = &a[3];
p2-p1 等於 3
- 指針的比較大小
指向同一數組的兩個指針變量可以比較大小:p2>p1
new
和delete
在定義變量或數組的同時即在內存爲其開闢了指定的固定空間。
一經定義,即爲固定地址的空間,在內存不能被別的變量所佔用。
利用new
運算符在堆內存中開闢一塊空間。
new
相當於一個函數,在內存開闢完空間後,返回這個空間的首地址,這時,這個地址必須用一個指針保存下來,纔不會丟失。
int *p = new int;
*p = 11;
或者
int *p = new int(11); //聲明時賦初值
那如何開闢多個連續的空間(數組)呢?
new 數據類型[個數];
int *pa = new int[4];
demo
int n;
cin>>n;
int *p = new int[n];
for(int i=0;i<n;i++)
{
cin>>p[i] //相當於 *(p+i)
}
注意:用new開闢的內存單元沒有名字,指向其首地址的指針是引用其的唯一途徑。若指針變量重新賦值,則用new開闢的內存單元就在內存中“丟失”了,別的程序也不能佔用這段單元(想delete都沒辦法),直到重新開機爲止。Heap corruption!
聯想:
在java
裏面,這些再也無法引用到的對象,會在某個時刻被jvm的GC自動回收。無需程序員主動釋放。
delete
運算符用來將動態分配到的內存空間歸還給系統。
delete收回用new開闢的單個空間。
int *p;
p = new int;
……
delete p;
//切記在delete前面,p不能重新賦值,否則就會heap corruption!
delete收回用new開闢的連續空間
加上 []
,否則只會釋放第一塊空間。
int *p = new int[10];
……
delete []p;
注意
- 同一塊空間不能被多次釋放
- 當內存中沒有足夠的空間給予分配時,new 運算符返回空指針NULL(0)。
int *p = new int;
*p = 5;
//一種安全的做法
if(!p){
delete p;
p = 0;
}
const
指針
const
常量
const double PI = 3.1415926;
用const定義的標識符常量,一定要對其初始化。在說明時進行初始化是對這種常量置值的唯一方法 。不能用賦值運算符再對這種常量進行賦值。
MAX_VALUE = 100; //error!
- 常指針(禁寫指針)
數據類型 * const 指針變量名 ;
如:
int r = 6;
int * const pr = &r;
注意const
的位置。
const
修飾的是指針,這表明指針值不能改變即pr將始終指向同一個地址,成爲一個指針常量。
禁寫指針一定要在定義的時候賦初值。
雖然指針被禁寫,但指針所指向的值並沒有。
*pr = 10 是被允許的。
- 指向常量的指針變量(禁寫間接引用)
const int * 指針變量名;
定義後,不允許通過指針變量改變所指向的對象的值。
但指針p並未被禁寫,可對指針p進行改寫。
對p所指向的變量也可以改變(只是不能通過指針)。
- 指針變量的相互賦值
不能把指向常量的指針賦值直接給一般指針(除非強制轉換),反過來可以。
int x = 6;
const int *px = &x;
int *px1;
px = px1;//ok
px1 = px //error
px1 = (int *)px; //ok
有這樣的語法也不難理解,
普通的指針賦給常量的指針賦值相當於多了一層保護,無風險。
但是反過來,相當於允許通過另外一個普通的指針修改值,這是有風險的,必須進行主動強制轉換。
- 指向常量的常指針變量
const 數據類型 const *指針變量名
定義後,不允許通過指針變量改變所指向的對象的值,也不允許改變指針變量的值。但是所指向的值也可以改變,如果它本身沒有被const
修飾。
int main()
{
int x = 1;
const int * const p = &x;
// p = new int; //違法
// *p = 11; //違法
x = 100;
return 0;
}
void*
指針
void *p;
無類型指針變量,指向空類型,或不指向確定的類型
使用時必須轉換。
int a=3;
int *p1=&a;
void * p2;
p2 = p1; //p2指向a的純地址,但不指向整型a
int * p3 = (int*)p2;
在想輸出字符指針的地址時,必須將char*
強制轉換爲void*
,因爲c++的流操作符對此進行了重載。
char * p = "a";
cout<<p<<endl;
cout<<(void*)p<<endl;
cout<< static_cast<void*>(p) <<endl;
a
0x100001f54
0x100001f54
引用
對變量起另外一個名字 (別名),這個名字稱爲該變量的引用。
<類型> & <引用變量名> = <原變量名>;
其中原變量名必須是一個已定義過的變量。
int a;
int &ref_a = a;
ref_a並沒有重新在內存中開闢單元,只是引用a的單元。a與ref_a在內存中佔用同一地址,即同一地址兩個名字。
- 引用在定義的時候要初始化。
int &ref_a; //error,沒有具體的引用對象
- 引用類型變量的初始化值不能是一個常數。
int &a = 1;//error
-
引用與變量一樣有地址,可以對其地址進行操作,即將其地址賦給一指針。
-
指針與引用的區別:
①指針是通過地址間接訪問某個變量,而引用是通過別名直接訪問某個變量。
②引用必須初始化,而一旦被初始化後不得再作爲其它變量的別名。
& 操作符的作用
如果&a的前面有類型符(如int &a),則是對引用的聲明;
如果&a的前面無類型符(如cout<<&a),則是取變量的地址。
1、不能建立引用的數組 int & a[9];
2、不能建立指向引用的指針 int & *p;
3、不能建立引用的引用 int & &px;
對3.的補充
int a = 1;
int &b = a;
int &c = b;
//這是允許的
引用作爲形參,實參是變量而不是地址,這與指針變量作形參不一樣。
用引用:
void f(int &a)
{
a*=2;
}
int main()
{
int a = 1 ;
f(a);
return 0;
}
用指針:
void f(int*pa)
{
*pa *=2;
}
int main()
{
int a = 1;
f(&a);
return 0;
}
常引用:
不能通過常引用修改其引用的變量。
int x = 1;
const int &a = x;
a = 5;//error;
x = 5;//ok
C++函數中返回引用和返回值的區別
函數返回值時會產生一個臨時變量作爲函數返回值的副本。
函數返回引用時不會產生值的副本,返回一個指向返回值的隱式指針。
當返回一個引用時,要注意被引用的對象不能超出作用域,返回一個對局部變量的引用是不合法的,除非此局部變量被聲明爲static
的。
這是很危險的操作,調用函數中對局部對象的引用會指向不確定的內存!
int & get(int &a)
{
int s = a;
return s;
}
正確的做法。
int & get(int &a)
{
static int s = a;
return s;
}
當然這樣是可以的。
int get(int &a)
{
int s = a;
return s;
}
當函數返回一個引用時,則返回一個指向返回值的隱式指針。
/函數就可以放在賦值語句的左邊。
#include <iostream>
using namespace std;
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
double& setValues( int i ) {
return vals[i]; // 返回第 i 個元素的引用
}
int main () {
cout << "改變前的值" << endl;
for ( int i = 0; i < 5; i++ ) {
cout << "vals[" << i << "] = " << vals[i] << endl;
}
//函數可以放在左邊
setValues(1) = 20.23; // 改變第 2 個元素
setValues(3) = 70.8; // 改變第 4 個元素
cout << "改變後的值" << endl;
for ( int i = 0; i < 5; i++ ) {
cout << "vals[" << i << "] = " << vals[i] << endl;
}
return 0;
}
-
數組的引用 、引用的數組
-
引用的數組是違法的
形如int &a[];
這相當於(int &)a[]
-
數組的引用是可行的
-
int a3[3] = {1,2,3};
int (&b3)[3] = a3;