1. 程式人生 > 其它 >[做題記錄-亂做] [AGC003E] Sequential operations on Sequence

[做題記錄-亂做] [AGC003E] Sequential operations on Sequence

1.取地址運算

  • scanf("%d",&i);裡的&
  • 獲得變數的地址,它的運算元必須是變數
    • int i; printf("%x",&i);

注意:

  • 獲取地址時,用%p來獲取
    • int i; printf("%p",&i);
  • 地址的大小是否與int相同取決於編譯器
#include <stdio.h>

int main()
{
	int a[10];
	printf("%p\n",&a);	
	printf("%p\n",a);
	printf("%p\n",&a[0]);
	printf("%p\n",&a[1]);		
	return 0;
}
000000000062FDF0
000000000062FDF0
000000000062FDF0
000000000062FDF4

1.1 &不能取的地址

&不能對沒有的東西取地址

比如:

  • &(a+b)
  • &(a++)
  • &(++a)

2.指標

定義:就是儲存地址的變數

int i;
int* p=&i;//這個p指向的是個int,把i的地址交給了這個p,這個*p只會有別的變數的地址
int* p,q;//q只是個普通的b	
int *p,q; 
  • 變數的值是記憶體的地址
  • 普通變數的值是實際的值
  • 指標變數的值是具有實際值的變數的地址

2.1 作為引數的指標

#include <stdio.h> 
void f(int *p);
void g(int k);
int main()
{
	int i = 6;
	printf("&i=%p\n",&i);
	f(&i);
	g(i); 
	return 0;
} 

void f(int *p)
{
	printf("p=%p\n",p);
}

void g(int k)
{
	printf("k=%d\n",k);//得到的只是i的值 
}
  • void f(int *p);

  • 在被呼叫的時候得到了某個變數的地址;

  • int i=0; f(&i);

  • 在函式裡面可以通過這個指標訪問外面的這個i

2.2 訪問那個地址上的變數*

是一個單目運算子,用來訪問指標的值所表示的地址上的變數*

可以做右值也可以做左值

  • int k= *p;
  • *p = k+1;
#include <stdio.h> 
void f(int *p);
void g(int k);
int main()
{
	int i = 6;
	f(&i);
	g(i); 
	return 0;
} 

void f(int *p)
{
	printf("p=%p\n",p);
	printf("*p=%d\n",*p); 
	*p = 26;//可以通過這種方式訪問外面的變數,可以修改,訪問 
}

void g(int k)
{
	printf("k=%d\n",k);//得到的只是i的值 
}

2.3 左值之所以叫左值

是因為出現在賦值好左邊的不是變數,而是值,是表示式計算的結果(運算的結果):

  • a[0]=2;
  • *p=3
  • 是特殊的值,所以叫做左值

2.4 指標的運算子&*

  • 互相反作用
    • *&yptr -> *(&yptr) -> *(yptr的地址) -> 得到那個地址上的變數 -> yptr
    • &*yptr -> &( *yptr) -> &(y) -> 得到y的地址,也就是yptr -> yptr

3.指標的使用

3.1 指標的應用場景一

  • 交換兩個變數的值(函式)

    #include <stdio.h>
    void swap(int *pa,int *pb);
    int main()
    {
    	int a=5;
    	int b=6; 
    	printf("a=%d,b=%d \n",a,b);
    	swap(&a,&b);
    	printf("a=%d,b=%d",a,b);
    	return 0;
     } 
     
    void swap(int *pa,int *pb)
    {
     	int t=*pa;
     	*pa=*pb;
     	*pb=t;
     }
    //a=5,b=6
    //a=6,b=5
    

3.2 指標的應用場景二

  • 函式返回多個值,某些值就只能通過指標返回
    • 傳入的引數實際上是是需要儲存帶回結果的變數
#include <stdio.h> 
void minmax(int a[],int len,int *min,int *max);

int main(void)
{
	int a[]={1,2,3,4,5,6,7,8,9,10};
	int min,max;
	minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);
	printf("min=%d,max=%d \n",min,max);
	return 0;
}

void minmax(int a[],int len,int *min,int *max)
{
	int i;
	*min =*max =a[0];
	for(i=1;i<len;i++){
		if(*min>a[i]){
			*min=a[i];
		}
		if(*max<a[i]){
			*max=a[i];
		}
	}
}

應用場景二b(運算出錯)

  • 函式返回運算的狀態,結果通過指標返回

  • 常用的套路是讓函式返回特殊的不屬於有效範圍內的值來表示出錯

    • -1或0(在檔案操作會看到大量的例子)
  • 但是當任何數值都是有效的可能結果時,就得分開返回

    #include <stdio.h>
    
    /*如果除法成功,返回1;否者返回0 */
     
    int divide(int a,int b,int *result);
    
    int main()
    {
    	int a=5;
    	int b=2;
    	int c;//c是a/b的結果 
    	if( divide(a,b,&c)){
    		printf("%d/%d=%d",a,b,c); 
    	} 
    	return 0;
    } 
    
    int divide(int a,int b,int *result)
    {
    	int ret =1;
    	if( b==0)ret=0;//除數不能是0 
    	else{
    		*result=a/b;
    	}
    	
    	return ret;
    	
    }
    

4.指標最常見的錯誤

  • 定義了指標變數,還沒有指向任何變數,就開始使用

5.傳入函式的陣列成了什麼?

函式引數表中的陣列實際上是指標

  • sizeof(a) == sizeof(int*)
  • 但是可以用陣列的運算子[]進行運算
#include <stdio.h> 

void minmax(int *a,int len,int *max,int *min);

int main(void)
{
	int a[] ={1,2,3,4,5,6,7,8,9,10};
	int min,max;
	printf("minmax sizeof(a)=%lu\n",sizeof(a));
	printf("main a=%p\n",a);
	minmax(a,sizeof(a)/sizeof(a[0]),&min,&max); 
	printf("a[0]=%d\n",a[0]);
	printf("min=%d,max=%d\n",min,max);

	return 0;
}

void minmax(int *a,int len,int *max,int *min)
{
	int i;
	printf("minmax sizeof(a)=%lu\n",sizeof(a));//接收到的是首地址 
	printf("main a=%p\n",a);
	a[0]=1000; 
	for ( i=1;i<len;i++){
		if( a[i]< *min){
			*min = a[i];
		}
		if( a[i]> *max){
			*max = a[i];
		}
	}
}
minmax sizeof(a)=40
main a=000000000062FDF0
minmax sizeof(a)=8
main a=000000000062FDF0
a[0]=1000
min=10,max=0

5.1 陣列的變數是特殊的指標

  • 陣列變數本身表達地址,所以不需要加

    • int a[10];int *p=a;//無需用&取地址
    • 但是陣列的單元表達的是變數,需要用&去地址
    • a == &a[0]
  • []運算子可以對陣列做,也可以對指標做:

    • p[0] <==> a[0]
  • *運算子可以對指標做,(取出所指變數例的值),也可以對陣列做

    • *a =25;
  • 陣列變數是const的指標,所以不能被賦值

    • int a[] <==> int * const a=...

6.指標與const

6.1 如果q是const

  • 表示q的值(即i的地址)不能被改變,就是說不能再指向別人了
    • int *const q =&i; //q是const
    • *q=26;//ok
    • q++;//ERROR

6.2 如果是const int

  • 表示
    • const int *p =&i;
    • p = 26;//ERROR! ( *P)是const
    • i = 26;
    • p =&j;

在前面就代表int不能被修改,在後面表示指標不能被修改。

6.3 轉換

  • 總是可以把一個非const的值轉換成const

    void f(const int* x);
    int a=15;
    f(&a);//ok
    const int b=1;
    
    f(&b);//ok
    b = a+1;//Error!
    
  • 當要傳遞的引數的型別比地址大的時候,這是常用的手段:既能用比較少的位元組數傳遞值給引數,又能避免函式對外面的變數的修改

6.4 const陣列

  • const int a[]={1,2,3,4,5,6};
  • 陣列變數已經全是const的指標了,這裡的const表明陣列的每個單元都是const
  • 所以必須通過初始化進行賦值

保護陣列值

  • 因為把陣列傳入函式時傳遞的是地址,所以那個函式內部可以修改陣列的值
  • 為了保護陣列不被函式破壞
    • int sum(const int a[],int length);

7.指標運算

這些算術運算可以對指標做:

  • 給指標加、減一個整數(+,+=,-,-=)
  • 遞增遞減(++/--)
  • 兩個指標相減,可以得到兩個地址差了幾個單元(地址相減/sizeof(資料型別))

7.1 *p++

  • 取出p所指的那個資料來,完事以後順便把p移到下個位置去
  • *的優先順序雖然高,但是沒有++搞
  • 常用於資料類的連續空間操作
  • 在某些CPU上,這可以直接被翻譯成一條彙編指令

7.2 指標比較

  • <,<=,==,>,>=,!= 都可以對指標做
  • 比較它們在記憶體中的地址
  • 陣列中的單元的地址肯定是線性遞增

7.3 0地址

  • 當然你的記憶體中有0地址,但是0地址通常是個不能隨便碰的地址

  • 所以你的指標不應該具有0值

  • 因此可以用0地址來表示特殊的事情

    • 返回的指標是無效的
    • 指標沒有被真正初始化(先初始化為0)
  • NULL是一個預定定義的符號

    • 有的編譯器不願意你來用0來表示0的地址

8. 指標的型別

  1. 無論指向什麼型別, 所有的指標的大小都是一樣的,因為都是地址
  2. 但是指向不同型別的指標是不能直接互相賦值的
  3. 這是為了避免用錯指標

9. 指標的型別轉換

  • void*表示不知道指向什麼東西的指標
    • 計算時與char*相同(但不通)
  • 指標也可以轉換型別
    • int *p = &i; void *q =(void *) p;
  • 這並沒有改變p所指的變數的型別,而是讓後人用不同眼光通過p看它所指的變數
  • 我不再當你是int啦,我認為你就是個void!

10. 總結

  1. 需要傳入較大的資料時用作引數
  2. 傳入陣列後對陣列做操作
  3. 函式返回不止一個結果
  4. 需要用函式來修改不止一個變數
  5. 動態申請記憶體時