c函數中返回字符串數組、char[]和char*的區別與轉換(詳細)

一、字符指針可以返回,而作爲局部變量的字符數組不能直接返回的原因

更多詳見參考博客:c函數中返回字符串數組

直接放一段代碼比較:

char * fork_user_name()
{
    char name[] = "veryitman";//!警告
    //!不應該返回一個局部變量 name 的地址(函數內部的變量在棧內存上)。
    return name;
}

char * fork_user_name2()
{
    char *name = "veryitman";
    return name;
}

int main()
{
    printf("fork_user_name: %s\n", fork_user_name());
    printf("fork_user_name2: %s\n", fork_user_name2());
    
    return 0;
}

結果:

fork_user_name: (null)
fork_user_name2: veryitman

結論:在函數中的局部變量只要是返回類似 int[]、char[]、long[] 地址的,都是不正確的做法。

二、如何返回局部變量的字符數組

1、使用 static

在C語言中,用 static 限定外部變量與函數,該外部變量或者函數除了對該所在的文件可見外,其他文件都無法訪問。 而用 static 聲明內部變量,則該變量是某個特定函數的局部變量,只能在該函數中使用。static 類型的內部變量是一種只能在某個特定函數中使用但一直佔據存儲空間的變量

所以使用static修飾一下,就沒有問題了。示例如下:

char * v_string()
{
    static char rest[10] = {'\0'};
    return rest;
}

2、使用 malloc

這種方式可以解決這個問題,是因爲使用 malloc 分配的內存是在堆上而不是在棧內存上面。但是要記得將其在調用方使用 free 釋放申請的內存空間,否則容易造成內存泄漏問題。

具體可以看看 雙宿雙飛的 malloc 和 free 這篇文章。

char * v_string()
{
    char *p = (char *)malloc(10 * sizeof(char));
    p = "\0";
    return p;
}

3、全局變量

這種方案就會讓這個封裝的方法不夠內聚,因爲它依賴了全局變量。

char g_rest[100];
char * v_string()
{
    strcpy(g_rest, "verytiamn");
    return g_rest;
}

4、返回形參指針變量

char * v_string(char *s1, char *s2)
{
    char *tmp = s1;
    // 省略...
    return tmp;
}

三、字符數組與字符指針的區別

此段轉自:https://blog.csdn.net/u012611878/article/details/78291036

相同點

首先 這兩種類型都可以對應一個字符串,比如:

char * a=”string1”;
char b[]=”string2”;
printf(“a=%s, b=%s”, a, b);

其中a是一個指向char變量的指針,b則是一個char數組(字符數組),

其次 ,很多時候二者可以混用,像函數傳參數的時候,實參可以是char*,形參可以是 char[],比如:

void fun1(char b[]){
printf("%s",b);
}
int main(){
char *a="HellowWorld";
fun1(a);
}

反過來,實參可以是char[],形參可以是 char *也是可以的。
存在即合理,char *和char[]肯定是有本質的不同。

不同點

1.char*是變量,值可以改變, char[]是常量,值不能改變。
比如:

char * a="string1";
char b[]="string2";
a=b; //OK
a="string3"; //OK
b=a; //報錯!左邊操作數只讀
b="string3"; //報錯!左邊操作數只讀

解釋: a是一個char型指針變量,其值(指向)可以改變; b是一個char型數組的名字,也是該數組首元素的地址,是常量,其值不可以改變 。

2.char[]對應的內存區域總是可寫,char*指向的區域有時可寫,有時只讀
比如:

char * a="string1";
char b[]="string2";
gets(a); //試圖將讀入的字符串保存到a指向的區域,運行崩潰!
gets(b) //OK

解釋: a指向的是一個字符串常量,即指向的內存區域只讀; b始終指向他所代表的數組在內存中的位置,始終可寫

注意,若改成這樣gets(a)就合法了:

char * a="string1";
char b[]="string2";
a=b; //a,b指向同一個區域,注意這裏改變了a的指向
gets(a) //OK
printf("%s",b) //會出現gets(a)時輸入的結果

解釋: a的值變成了是字符數組首地址,即&b[0],該地址指向的區域是char *或者說
char[8],習慣上稱該類型爲字符數組,其實也可以稱之爲“字符串變量”,區域可讀可寫。

注意:char *本身是一個字符指針變量,但是它既可以指向字符串常量,又可以指向字符串變量,指向的類型決定了對應的字符串能不能改變。

3.char * 和char[]的初始化操作有着根本區別:

char *a="Hello World";
char b[]="Hello World";
printf("%s, %d\n","Hello World", "Hello World");
printf("%s, %d %d\n", a, a, &a);
printf("%s, %d %d\n", b, b, &b);

在這裏插入圖片描述
結果可見:儘管都對應了相同的字符串,但”Hellow World”的地址 和 a對應的地址相同,與b指向的地址有較大差異;&a 、&b都是在同一內存區域,且&bb
根據c內存區域劃分知識,我們知道,局部變量都創建在棧區,而常量都創建在文字常量區,顯然,a、b都是棧區的變量,但是a指向了常量(字符串常量),b則指向了變量(字符數組),指向了自己(&b
b==&b[0])。

說明以下問題:

  • char * a=”string1”;是實現了3個操作:

1聲明一個char*變量(也就是聲明瞭一個指向char的指針變量)。
2在內存中的文字常量區中開闢了一個空間存儲字符串常量”string1”。 3返回這個區域的地址,作爲值,賦給這個字符指針變量a
最終的結果:指針變量a指向了這一個字符串常量“string1” (注意,如果這時候我們再執行:char *
c=”string1”;則,c==a,實際上,只會執行上述步驟的1和3,因爲這個常量已經在內存中創建)

  • char b[]=”string2”;則是實現了2個操作:

1聲明一個char 的數組, 2爲該數組“賦值”,即將”string2”的每一個字符分別賦值給數組的每一個元素,存儲在棧上。
最終的結果:“數組的值”(注意不是b的值)等於”string2”,而不是b指向一個字符串常量

PS:
實際上, char * a=”string1”; 的寫法是不規範的!
因爲a指向了即字符常量,一旦strcpy(a,”string2”)就糟糕了,試圖向只讀的內存區域寫入,程序會崩潰的!儘管VS下的編譯器不會警告,但如果你使用了語法嚴謹的Linux下的C編譯器GCC,或者在windows下使用MinGW編譯器就會得到警告。

所以,我們還是應當按照”類型相同賦值”的原則來寫代碼:

const char * a=”string1”;

保證意外賦值語句不會通過編譯。

小結

對於

const char * a="string1"
char b[]="string2";

1.a是const char 類型, b是char const類型 ( 或者理解爲 (const char)xx 和 char (const xx) )

2.a是一個指針變量,a的值(指向)是可以改變的,但a只能指向(字符串)常量,指向的區域的內容不可改變;

3.b是一個指針常量,b的值(指向)不能變;但b指向的目標(數組b在內存中的區域)的內容是可變

4.作爲函數的聲明的參數的時候,char []是被當做char *來處理的!兩種形參聲明寫法完全等效

四、字符數組和字符指針的轉換

char [] 轉 char *

可以直接進行賦值,示例如下:

int main()
{
	char c_str_array[] = "veryitman.com";
    char *p_str;
    p_str = c_str_array;
    printf("p_str: %s\n", p_str);
	return 0;
}

char*轉char[]

不能直接向上面那樣轉,可使用strnpy

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

int main()
{
    char c_str_array[] = "veryitman.com";
    char *p_str = "veryitman.com";
    strncpy(c_str_array, p_str, strlen(p_str));
    //strncpy把源字符串的字符複製到目標數組。然而,它總是正好向dst寫入len個字符。如果strlen(src)的值小於len,dst數組就用額外的NUL字節填充到len長度,如果strlen(src)的值大於或等於len,那麼只有len個字符被複制到dst中
    printf("c_str_array: %s\n", c_str_array);
    return 0
}

PS:strncpy並沒有拷貝串後的\0字符,而strcpy卻拷貝了。這充分說明,strncpy是爲拷貝字符而生的,而strcpy是拷貝字符串而生的。但兩者都不能越界拷貝。

strnpy詳解見:深入理解strncpy這個函數
別再耍流氓了: 請別再用strcpy, 而用strncpy

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章