C/C++中static的用法全局變量與局部變量

轉載自C/C++中static的用法全局變量與局部變量

1.什么是static?

  static?是C/C++中很常用的修飾符,它被用來控制變量的存儲方式和可見性。

1.1static的引入

  我們知道在函數內部定義的變量,當程序執行到它的定義處時,編譯器為它在棧上分配空間,函數在棧上分配的空間在此函數執行結束時會釋放掉,這樣就產生了一個問題:?如果想將函數中此變量的值保存至下一次調用時,如何實現? 最容易想到的方法是定義為全局的變量,但定義一個全局變量有許多缺點,最明顯的缺點是破壞了此變量的訪問范圍(使得在此函數中定義的變量,不只受此函數控制)。static關鍵字則可以很好的解決這個問題。

另外,在C++中,需要一個數據對象為整個類而非某個對象服務,同時又力求不破壞類的封裝性,即要求此成員隱藏在類的內部,對外不可見時,可將其定義為靜態數據。

1.2靜態數據的存儲

  全局(靜態)存儲區:分為DATA段和BSS段。DATA段(全局初始化區)存放初始化的全局變量和靜態變量;BSS段(全局未初始化區)存放未初始化的全局變量和靜態變量。程序運行結束時自動釋放。其中BSS段在程序執行之前會被系統自動清0,所以未初始化的全局變量和靜態變量在程序執行之前已經為0。存儲在靜態數據區的變量會在程序剛開始運行時就完成初始化,也是唯一的一次初始化。

在C++中static的內部實現機制:靜態數據成員要在程序一開始運行時就必須存在。因為函數在程序運行中被調用,所以靜態數據成員不能在任何函數內分配空間和初始化。
???????這樣,它的空間分配有三個可能的地方,一是作為類的外部接口的頭文件,那里有類聲明;二是類定義的內部實現,那里有類的成員函數定義;三是應用程序的main()函數前的全局數據聲明和定義處。
??????靜態數據成員要實際地分配空間,故不能在類的聲明中定義(只能聲明數據成員)。類聲明只聲明一個類的“尺寸和規格”,并不進行實際的內存分配,所以在類聲明中寫成定義是錯誤的。它也不能在頭文件中類聲明的外部定義,因為那會造成在多個使用該類的源文件中,對其重復定義。
????? static被引入以告知編譯器,將變量存儲在程序的靜態存儲區而非棧上空間,靜態數據成員按定義出現的先后順序依次初始化,注意靜態成員嵌套時,要保證所嵌套的成員已經初始化了。消除時的順序是初始化的反順序。
優勢:可以節省內存,因為它是所有對象所公有的,因此,對多個對象來說,靜態數據成員只存儲一處,供所有對象共用。靜態數據成員的值對每個對象都是一樣,但它的值是可以更新的。只要對靜態數據成員的值更新一次,保證所有對象存取更新后的相同的值,這樣可以提高時間效率。

2.在C/C++中static的作用

2.1總的來說:

(1)生命周期:在修飾變量的時候,static修飾的靜態局部變量只執行初始化一次,而且延長了局部變量的生命周期,直到程序運行結束以后才釋放,但不改變作用域。比如修飾函數中存放在棧空間的數組。如果不想讓這個數組在函數調用結束釋放可以使用static修飾。
(2)可見性:static修飾全局變量或函數時,這個全局變量只能在本文件中訪問,不能在其它文件中訪問,即便是extern外部聲明也不可以。這個函數也只能在本文件中調用,不能被其他文件調用。
(3)存儲方式:Static修飾的變量存放在全局數據區的靜態變量區,包括全局靜態變量和局部靜態變量,都在全局數據區分配內存。初始化的時候自動初始化為0。
(4)考慮到數據安全性(當程序想要使用全局變量的時候應該先考慮使用static)。

2.2靜態變量與普通變量

靜態全局變量有以下特點:
(1)靜態變量都在全局數據區分配內存,包括后面將要提到的靜態局部變量;
(2)未經初始化的靜態全局變量會被程序自動初始化為0(在函數體內聲明的自動變量的值是隨機的,除非它被顯式初始化,而在函數體外被聲明的自動變量也會被初始化為0);
(3)靜態全局變量在聲明它的整個文件都是可見的,而在文件之外是不可見的。
優點:靜態全局變量不能被其它文件所用;其它文件中可以定義相同名字的變量,不會發生沖突。

(4)全局變量和全局靜態變量的區別

1)全局變量是不顯式用static修飾的全局變量,全局變量默認是有外部鏈接性的,作用域是整個工程,在一個文件內定義的全局變量,在另一個文件中,通過extern 全局變量名的聲明,就可以使用全局變量。
2)全局靜態變量是顯式用static修飾的全局變量,作用域是聲明此變量所在的文件,其他的文件即使用extern聲明也不能使用。

2.3靜態局部變量有以下特點:

(1)該變量在全局數據區分配內存;
(2)靜態局部變量在程序執行到該對象的聲明處時被首次初始化,即以后的函數調用不再進行初始化;
(3)靜態局部變量一般在聲明處初始化,如果沒有顯式初始化,會被程序自動初始化為0;
(4)它始終駐留在全局數據區,直到程序運行結束。但其作用域為局部作用域,當定義它的函數或語句塊結束時,其作用域隨之結束。
一般程序把新產生的動態數據存放在堆區,函數內部的自動變量存放在棧區。自動變量一般會隨著函數的退出而釋放空間,靜態數據(即使是函數內部的靜態局部變量)也存放在全局數據區。全局數據區的數據并不會因為函數的退出而釋放空間。
//example:
#include <stdio.h>
#include <stdlib.h>
int k1 = 1;
int k2;
static int k3 = 2;
static int k4;
int main()
{static int m1 = 2, m2;int i = 1;char*p;char str[10] = "hello";char*q = "hello";p = (char *)malloc(100);free(p);printf("棧區-變量地址    i:%p\n", &i);printf("棧區-變量地址   p:%p\n", &p);printf("棧區-變量地址 str:%p\n", str);printf("棧區-變量地址   q:%p\n", &q);printf("堆區地址-動態申請:%p\n", p);printf("全局外部有初值 k1:%p\n", &k1);printf("   外部無初值 k2:%p\n", &k2);printf("靜態外部有初值 k3:%p\n", &k3);printf("   外靜無初值 k4:%p\n", &k4);printf("  內靜態有初值 m1:%p\n", &m1);printf("  內靜態無初值 m2:%p\n", &m2);printf("    文字常量地址:%p, %s\n", q, q);printf("      程序區地址:%p\n", &main);return 0;
}

3.1特別的,在C++中:

static關鍵字最基本的用法是:

1、被static修飾的變量屬于類變量,可以通過類名.變量名直接引用,而不需要new出一個類來

2、被static修飾的方法屬于類方法,可以通過類名.方法名直接引用,而不需要new出一個類來

被static修飾的變量、被static修飾的方法統一屬于類的靜態資源,是類實例之間共享的,換言之,一處變、處處變。

  在C++中,靜態成員是屬于整個類的而不是某個對象,靜態成員變量只存儲一份供所有對象共用。所以在所有對象中都可以共享它。使用靜態成員變量實現多個對象之間的數據共享不會破壞隱藏的原則,保證了安全性還可以節省內存。

靜態成員的定義或聲明要加個關鍵static。靜態成員可以通過雙冒號來使用即<類名>::<靜態成員名>。

3.2靜態類相關

//example1:通過類名調用靜態成員函數和非靜態成員函數class Point{public:void init(){}static void output(){}};void main(){Point::init();Point::output();}

報錯: 'Point::init' : illegal call of non-static member function

結論1:不能通過類名來調用類的非靜態成員函數。

//example2:通過類的對象調用靜態成員函數和非靜態成員函數class Point{public:void init(){}static void output(){}};
void main()
{Point pt;pt.init();pt.output();
}

編譯通過。

結論2:類的對象可以使用靜態成員函數和非靜態成員函數。

//example3:在類的靜態成員函數中使用類的非靜態成員
#include <stdio.h>
class Point
{
public:void init(){}static void output(){printf("%d\n", m_x);}
private:int m_x;
};
void main()
{Point pt;pt.output();
}

編譯出錯:error C2597: illegal reference to data member 'Point::m_x' in a static member function

  因為靜態成員函數屬于整個類,在類實例化對象之前就已經分配空間了,而類的非靜態成員必須在類實例化對象后才有內存空間,所以這個調用就出錯了,就好比沒有聲明一個變量卻提前使用它一樣。

結論3:靜態成員函數中不能引用非靜態成員。

//example4:在類的非靜態成員函數中使用類的靜態成員
class Point
{
public:void init(){output();}static void output(){}
};
void main()
{Point pt;  Pt.init();pt.output();
}

編譯通過。

結論4:類的非靜態成員函數可以調用用靜態成員函數,但反之不能。

//example5:使用類的靜態成員變量#include <stdio.h>class Point{public:Point(){m_nPointCount++;}~Point(){m_nPointCount--;}static void output(){printf("%d\n", m_nPointCount);}private:static int m_nPointCount;};void main(){Point pt;pt.output();}

按Ctrl+F7編譯無錯誤,按F7生成EXE程序時報鏈接錯誤

error LNK2001: unresolved external symbol "private: static int Point::m_nPointCount" (?m_nPointCount@Point@@0HA)

這是因為類的靜態成員變量在使用前必須先初始化。

在main()函數前加上int Point::m_nPointCount = 0;

再編譯鏈接無錯誤,運行程序將輸出1。

結論5:類的靜態成員變量必須先初始化再使用。

  思考總結:靜態資源屬于類,但是是獨立于類存在的。從J類的加載機制的角度講,靜態資源是類初始化的時候加載的,而非靜態資源是類實例化對象的時候加載的。 類的初始化早于類實例化對象,比如Class.forName(“xxx”)方法,就是初始化了一個類,但是并沒有實例化對象,只是加載這個類的靜態資源罷 了。所以對于靜態資源來說,它是不可能知道一個類中有哪些非靜態資源的;但是對于非靜態資源來說就不一樣了,由于它是實例化對象出來之后產生的,因此屬于類的這些東西它都能認識。所以上面的幾個問題答案就很明確了:

1)靜態方法能不能引用非靜態資源?不能,實例化對象的時候才會產生的東西,對于初始化后就存在的靜態資源來說,根本不認識它。

2)靜態方法里面能不能引用靜態資源?可以,因為都是類初始化的時候加載的,大家相互都認識。

3)非靜態方法里面能不能引用靜態資源?可以,非靜態方法就是實例方法,那是實例化對象之后才產生的,那么屬于類的內容它都認識。

  (static修飾類:這個用得相對比前面的用法少多了,static一般情況下來說是不可以修飾類的, 如果static要修飾一個類,說明這個類是一個靜態內部類(注意static只能修飾一個內部類),也就是匿名內部類。像線程池 ThreadPoolExecutor中的四種拒絕機制CallerRunsPolicy、AbortPolicy、DiscardPolicy、 DiscardOldestPolicy就是靜態內部類。靜態內部類相關內容會在寫內部類的時候專門講到。)

3.3總結:

(1)靜態成員函數中不能調用非靜態成員。

(2)非靜態成員函數中可以調用靜態成員。因為靜態成員屬于類本身,在類的對象產生之前就已經存在了,所以在非靜態成員函數中是可以調用靜態成員的。

(3)靜態成員變量使用前必須先初始化(如int MyClass::m_nNumber = 0;),否則會在linker時出錯。

參考:http://blog.csdn.net/morewindows/article/details/6721430

?一般總結:在類中,static可以用來修飾靜態數據成員和靜態成員方法
靜態數據成員
(1)靜態數據成員可以實現多個對象之間的數據共享,它是類的所有對象的共享成員,它在內存中只占一份空間,如果改變它的值,則各對象中這個數據成員的值都被改變。
(2)靜態數據成員是在程序開始運行時被分配空間,到程序結束之后才釋放,只要類中指定了靜態數據成員,即使不定義對象,也會為靜態數據成員分配空間。
(3)靜態數據成員可以被初始化,但是只能在類體外進行初始化,若未對靜態數據成員賦初值,則編譯器會自動為其初始化為0
(4)靜態數據成員既可以通過對象名引用,也可以通過類名引用。

靜態成員函數
(1)靜態成員函數和靜態數據成員一樣,他們都屬于類的靜態成員,而不是對象成員。
(2)非靜態成員函數有this指針,而靜態成員函數沒有this指針。
(3)靜態成員函數主要用來方位靜態數據成員而不能訪問非靜態成員。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/444174.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/444174.shtml
英文地址,請注明出處:http://en.pswp.cn/news/444174.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

查看商品圖片,鼠標懸浮圖片放大js實現

2010-06-07 10:18:46|分類&#xff1a;Javascript|字號訂閱 <%pagelanguage"java"import"java.util.*"pageEncoding"UTF-8"%> <%pageimport"com.pojo.Products"%> <% String path request.getContextPath(); String b…

leetcode547. 朋友圈

班上有 N 名學生。其中有些人是朋友&#xff0c;有些則不是。他們的友誼具有是傳遞性。如果已知 A 是 B 的朋友&#xff0c;B 是 C 的朋友&#xff0c;那么我們可以認為 A 也是 C 的朋友。所謂的朋友圈&#xff0c;是指所有朋友的集合。 給定一個 N * N 的矩陣 M&#xff0c;表…

C++中volatile關鍵字

轉載https://blog.csdn.net/weixin_44363885/article/details/92838607 一、volatile介紹 volatile提醒編譯器它后面所定義的變量隨時都有可能改變&#xff0c;因此編譯后的程序每次需要存儲或讀取這個變量的時候&#xff0c;都會直接從變量地址中讀取數據。如果沒有volatile…

leetcode261. 以圖判樹

給定從 0 到 n-1 標號的 n 個結點&#xff0c;和一個無向邊列表&#xff08;每條邊以結點對來表示&#xff09;&#xff0c;請編寫一個函數用來判斷這些邊是否能夠形成一個合法有效的樹結構。 示例 1&#xff1a; 輸入: n 5, 邊列表 edges [[0,1], [0,2], [0,3], [1,4]] 輸…

leetcode323. 無向圖中連通分量的數目

給定編號從 0 到 n-1 的 n 個節點和一個無向邊列表&#xff08;每條邊都是一對節點&#xff09;&#xff0c;請編寫一個函數來計算無向圖中連通分量的數目。 示例 1: 輸入: n 5 和 edges [[0, 1], [1, 2], [3, 4]] 0 3 | | 1 --- 2 4 輸出…

leetcode79. 單詞搜索 網格地圖搜索+回溯經典寫法啦

給定一個二維網格和一個單詞&#xff0c;找出該單詞是否存在于網格中。 單詞必須按照字母順序&#xff0c;通過相鄰的單元格內的字母構成&#xff0c;其中“相鄰”單元格是那些水平相鄰或垂直相鄰的單元格。同一個單元格內的字母不允許被重復使用。 示例: board [ [A,B,C…

leetcode1075. 項目員工 I(SQL)

項目表 Project&#xff1a; ---------------------- | Column Name | Type | ---------------------- | project_id | int | | employee_id | int | ---------------------- 主鍵為 (project_id, employee_id)。 employee_id 是員工表 Employee 表的外鍵。 員工…

leetcode1082. 銷售分析 I (SQL)

產品表&#xff1a;Product ----------------------- | Column Name | Type | ----------------------- | product_id | int | | product_name | varchar | | unit_price | int | ----------------------- product_id 是這個表的主鍵. 銷售表&#xff1a;Sale…

Oracle 獲取當前日期及日期格式

Oracle 獲取當前日期及日期格式 <wbr><wbr> 獲取系統日期&#xff1a;<wbr><wbr><span style"padding:0px; margin:0px; color:rgb(255,0,0)">SYSDATE()<br style"padding-bottom:0px; padding-top:0px; padding-left:0px; ma…

leetcode1083. 銷售分析 II(SQL)

Table: Product ----------------------- | Column Name | Type | ----------------------- | product_id | int | | product_name | varchar | | unit_price | int | ----------------------- product_id 是這張表的主鍵 Table: Sales --------------------…

leetcode1084. 銷售分析III(SQL)

Table: Product ----------------------- | Column Name | Type | ----------------------- | product_id | int | | product_name | varchar | | unit_price | int | ----------------------- product_id 是這個表的主鍵 Table: Sales --------------------…

炸窩(Java)拼接

數組中插入相關練習 例題&#xff1a;定義一個方法 &#xff0c;將數組{1,2,3}按照指定的格式進行拼接成一個字符串 /*例題&#xff1a;定義一個方法 &#xff0c;將數組{1,2,3}按照指定的格式進行拼接成一個字符串&#xff0c; 格式定義如下[word1#word2#word3]. 思路分析&a…

leetcode71. 簡化路徑 Unix 風格

以 Unix 風格給出一個文件的絕對路徑&#xff0c;你需要簡化它。或者換句話說&#xff0c;將其轉換為規范路徑。 在 Unix 風格的文件系統中&#xff0c;一個點&#xff08;.&#xff09;表示當前目錄本身&#xff1b;此外&#xff0c;兩個點 &#xff08;..&#xff09; 表示將…

李牛(Linux)腳本

Linux課堂筆記day01 主要總結內容&#xff1a; 一&#xff1a;Linux背景介紹 二&#xff1a;系統操作 三&#xff1a;服務管理 四&#xff1a;shell腳本 五&#xff1a;文本操作 六:常用服務搭建 01&#xff1a;初識linux 收獲&#xff1a;可以熟練應對運維和開發 對以后的生…

leetcode601. 體育館的人流量(SQL)

X 市建了一個新的體育館&#xff0c;每日人流量信息被記錄在這三列信息中&#xff1a;序號 (id)、日期 (visit_date)、 人流量 (people)。 請編寫一個查詢語句&#xff0c;找出人流量的高峰期。高峰期時&#xff0c;至少連續三行記錄中的人流量不少于100。 例如&#xff0c;表…

李牛(Linux)打包

15&#xff1a;打包壓縮以及解壓縮 接下來我們來介紹打包壓縮以及解壓縮命令 首先我們要在腦海里想幾個問題&#xff1a; 1.打包壓縮以及解壓縮在字面上理解到底是什么意思&#xff1f; 是不是像我們生活見到的事例那樣 比如說&#xff1a;生產酒的廠商一般都是按照規則將12瓶…

notepad++ 文本文件內容丟失恢復

今天用著notepad不知道怎的&#xff0c;突然就崩潰了&#xff0c;然后我下次打開的時候彈了個框&#xff0c;我按了OK之后&#xff0c;里面所有的內容都不見了 網上百度了半天&#xff0c;總結如下&#xff1a; 在如下目錄下有notepad會自動保存的文件 C:\Users\Administrato…

jquery實現頁面提示,數據正在加載中。(

簡單代碼&#xff1a; jsp中代碼如下&#xff1a;<wbr> <div id"dataLoad" style"display:none"><!--頁面載入顯示--></wbr><wbr><wbr><table width100% height100% border0 aligncenter valignmiddle></wbr…

李牛(Linux)vi

16&#xff1a;強大的vi 引言&#xff1a;提到vi我們不得不提到vim 這兩種編輯器就先當于我們Windows操作系統當中的記事本 不過vi以及vim編輯器熟練掌握之后是不需使用鼠標進行操作的 完全都是由鍵盤來進行控制 那為什么可以不用鼠標呢 就是因為我們的vi編輯器是基于多模式的…

(多線程)leetcode1114. 按序打印 認識AtomicInteger

我們提供了一個類&#xff1a; public class Foo { public void one() { print("one"); } public void two() { print("two"); } public void three() { print("three"); } } 三個不同的線程將會共用一個 Foo 實例。 線程 A 將會調用 on…