第0講 開山篇
讀前介紹:本文中以下文本格式是超連接,能夠點擊跳轉
>>超連接<<css
個人學習目標:基礎要堅如磐石 代碼要十份規範 筆記要認真詳實html
1、java內容介紹
java編程能夠分紅三個方向
①java se(j2se) 桌面開發前端
②java ee (j2ee)web開發java
③java me (j2me) 手機開發node
java SE是基礎中的基礎ios
2、java SE課程介紹
java SE 包括如下幾個部分c++
□ java 面向對象編程【核心中的核心,重點中的重點】程序員
□java 圖形界面web
□java數據庫編程 【經過滿漢樓系統進行講解】面試
□java 文件io流
□java 網絡編程 【會拿山寨版的QQ來說】
□java多線程 【經過坦克大戰進行講解,仔細瞭解該項目特色】
3、如何學習課程
一、高效而愉快的學習【高效:老師帶你,你隨着老師走下來】
二、先創建一個總體框架,而後細節
三、用到什麼再學什麼
四、先know how,再know Why
五、軟件編程是一個「作中學」的課程,不是會了再作,而是作了纔會
6、適當的囫圇吞棗,不要在某個點抓着不放
七、學習軟件編程是在琢磨別人怎麼作,而不是我認爲應該怎麼作的過程
4、java EE課程介紹
①、java EE基礎(1)
java面向對象編程、數據庫編程(sql server,oracle)————》》java se 【十分十分重要】
②、java EE基礎(2)
html、css、JavaScript————》》div+css
③、java ee中級部分
servlet、Jsp————》》mvc模式
④、java ee高級部分
Struts、Ejb、Hibernate、Spring、Ajax(ext,dw2)————》》ssh框架
5、最終目標
最後當咱們學完整個體系以後,腦殼裏應該造成下圖所示的層次模塊

這個體系包括Client(就是用戶看到的內容)、Web(服務器端)、Business(業務)、DB(數據庫)四部分構成。但願努力學習。
第1講 內容介紹,項目演示和原理剖析
1、課程介紹
□ java 面向對象編程【核心中的核心,重點中的重點】
□java 圖形界面 【圖形界面不是java的強項,在圖形化界面上Delphi和C++Builder不錯,java在後臺是強項 】
□java數據庫編程 【經過滿漢樓系統進行講解,若是一個項目不和數據庫鏈接起來基本沒多大意義】
□java 文件io流 【java如何進行文件操做】
□java 網絡編程 【會拿山寨版的QQ來說,傳輸的內容如視頻,語音,文字和圖片等】
山寨版QQ項目效果演示

□java多線程 【經過坦克大戰進行講解,仔細瞭解韓老師的該項目特色】
坦克大戰項目效果演示【對面向對象和多線程進行學習】

2、java介紹
一、java是什麼?
java是一種語言。中國人和中國人之間的交流是依靠漢語,而計算機和人之間依靠的是計算機語言,而java就是衆多編程語言中的一個。
編程語言排行榜網址:>>https://www.tiobe.com/tiobe-index/ <<
2019年2月編程語言排行榜(數據來源TIOBE)
Feb 2019 |
Feb 2018 |
Change |
Programming Language |
Ratings |
Change |
1 |
1 |
|
Java |
15.876% |
+0.89% |
2 |
2 |
|
C |
12.424% |
+0.57% |
3 |
4 |
 |
Python |
7.574% |
+2.41% |
4 |
3 |
 |
C++ |
7.444% |
+1.72% |
5 |
6 |
 |
Visual Basic .NET |
7.095% |
+3.02% |
6 |
8 |
 |
JavaScript |
2.848% |
-0.32% |
7 |
5 |
 |
C# |
2.846% |
-1.61% |
8 |
7 |
 |
PHP |
2.271% |
-1.15% |
9 |
11 |
 |
SQL |
1.900% |
-0.46% |
10 |
20 |
 |
Objective-C |
1.447% |
+0.32% |
11 |
15 |
 |
Assembly language |
1.377% |
-0.46% |
12 |
19 |
 |
MATLAB |
1.196% |
-0.03% |
13 |
17 |
 |
Perl |
1.102% |
-0.66% |
14 |
9 |
 |
Delphi/Object Pascal |
1.066% |
-1.52% |
15 |
13 |
 |
R |
1.043% |
-1.04% |
16 |
10 |
 |
Ruby |
1.037% |
-1.50% |
17 |
12 |
 |
Visual Basic |
0.991% |
-1.19% |
18 |
18 |
|
Go |
0.960% |
-0.46% |
19 |
49 |
 |
Groovy |
0.936% |
+0.75% |
20 |
16 |
 |
Swift |
0.918% |
-0.88% |
二、java的產生
首先認識一下java創始人:James Gosling

在1990年,sun公司啓動了一個項目計劃,叫綠色計劃,想寫一種語言去控制電視機的機頂盒,當時沒有對該語言起名,當時該語言市場不大,因而把這種語言用來控制家用電器,如空調,冰箱洗衣機等進行必定的編程。後來在1992年對該語言起名爲oak,由於sun公司在美國硅谷,那裏橡樹比較多。後來又對語言名稱進行更改成silk,可是該名稱在美國屬於專業術語,因爲美國許多程序員喜歡喝咖啡,因而起名爲java。在1994年gosling參加了硅谷大會,演示java功能,他當時用java寫了一個瀏覽器,名稱是>>HotJava Browser<<,他用他的瀏覽器輸入一個網址一回車出現了一個動態的網頁,震驚世界。因而SUN公司搭建了一個FTP服務器把java公開免費下載,java市場得以推廣。1995年SUN公司正式把該語言命名爲java。
java版本目前(截止到2019年2月21日)更新到11.0版本
歷史版本更新狀況以下

3、java開發工具的介紹
①、Java開發工具
△記事本
△jcreateor 【已退出歷史舞臺】
△jbuilder 【也已退出歷史舞臺】
△netbean
△eclipse【推薦】
各類開發工具的圖標以下:

②、如何選擇開發工具
學習的開始階段先選擇使用記事本,培養寫代碼的格式和感受,當對Java具備必定的瞭解後在轉換成高級開發工具Eclipse。
③、爲這麼這麼選擇
①、更深入的理解Java技術,培養代碼感。
②、有利於公司面試。面試的時候公司會要求被面試者用紙和筆把程序寫下來。
4、Java語言的特色
□Java語言是簡單的
Java語言相對於其餘語言來講比較簡單。
□Java語言是面向對象的
面向對象的意思目前很差說,只須要記住,在之後的學習中在慢慢體會。
□Java語言是跨平臺(操做系統)的【即一次編譯,處處運行】
這裏首先說明一下,Java語言是跨平臺的,可是他的跨平臺行依賴於JVM,JVM是不跨平臺的【後續瞭解】。
常見的平臺有如下類型

□Java是高性能的
5、第一個java程序(HelloWorld)[經過該案例講解在java程序運行原理]
一、瞭解Java的開發運行環境【即JDK[全程Java Development Kit]】。
什麼是JDK? 所謂JDK就是Java開發運行環境,包括Java開發工具和Java運行時環境【JRE 全稱Java Runtime Environment】,而JRE又包括Java虛擬機【JVM 全稱Java Virtual Machine】和Java自帶的標準類庫【存在於Java安裝目錄下的lib文件夾中】。在不一樣平臺【即操做系統】有不一樣的JVM。咱們須要根據本身的平臺下載與本身平臺相匹配的JDK進行安裝。
什麼是JVM?JVM是Java Virtual Machine(Java>>虛擬機<<)的縮寫,JVM是一種用於計算設備的規範,它是在已安裝JDK的平臺上虛構出來的計算機,是經過在實際的計算機上仿真模擬各類計算機功能來實現的。>>JVM虛擬機拓展閱讀
什麼是JRE?JRE是Java Runtime Environment(Java運行環境)的縮寫,是用於Java程序的運行環境,包括JVM和核心類庫。不作開發的平臺能夠只單獨安裝JRE來運行Java程序。
總結:咱們想要運行一個已有的Java程序,那麼只需安裝 JRE 便可。 咱們想要開發一個全新的Java程序,那麼必須安裝 JDK ,其內部包含 JRE 。
二、安裝JDK
一、下載JDK
JDK的下載網址爲:>>點擊跳轉至JDK下載頁<<
該頁面【跳轉後的官網頁面】提供JDK不一樣版本的下載,本人下載的是JDK8.0的.exe安裝程序,在下方的安裝中以JDK8.0的安裝做爲安裝流程的講解【本人平臺WIndows版本,64位系統】。
二、安裝JDK
第一步、雙擊下載後的安裝程序,在出現的窗口中點擊"下一步"。

第二步、安裝路徑
此處的安裝路徑務必記住!!!!默認路徑爲黃色方框的路徑,能夠不更改,直接點擊下一步,如需更改安裝路徑可點擊紅色方框的更改按鈕,此處我安裝時選擇了更改安裝路徑。務必注意!

點擊更改按鈕後會出現下方的對話框,下方爲我修改後的安裝路徑,此路徑記下,做爲配置環境變量的參數。

高能提示:安裝路徑中,建議不要包含中文和空格。jre能夠不用安裝了,緣由是JDK中已經包含了jRE。
後續操做都是點「肯定」或者「下一步」,傻瓜操做再也不贅述。
三、配置環境變量
一、爲何要配置環境變量
開發Java程序,須要使用JDK中提供的工具,工具在JDK 1.8安裝目錄的 bin 目錄下。在DOS命令行下使用這些工具,就要先進入到JDK的bin目錄下,這個過程就會很是的麻煩。不進入JDK的 bin 目錄,這些工具就不能使用,會報錯。爲了開發方便,咱們想在任意的目錄下均可以使用JDK的開發工具,則必需要配置環境變量,配置環境變量的意義 在於告訴操做系統,咱們使用的JDK開發工具在哪一個目錄下。
二、如何配置環境變量
①、Windows7/8版本



此處的安裝目錄務必是你本身的安裝目錄




若是運行javac以後出現以上一大段內容,恭喜你Java的安裝和配置已經成功!
②、Windows 10版本
1. 文件資源管理器 --> 此電腦鼠標右鍵 --> 選擇 屬性【或者在電腦桌面上的「此電腦」【注意:桌面上的此電腦圖標左下角務必不能有箭頭】右鍵選擇屬性再或者在鍵盤上同時按下「Windows按鍵」+E,在出現的對話框左側找到「此電腦」而後在上面右鍵選擇屬性。】

此處的安裝目錄務必是你本身的安裝目錄。



若是運行javac以後出現以上一大段內容,恭喜你Java的安裝和配置已經成功!
四、JDK再解釋
JDK中包含JRE和Java的工具,Java的編譯器javac.exe和Java的解釋器java.exe,在java的類庫中有3600多個類,可是咱們經常使用的類有150個,咱們只需把這150個類掌握就能夠成爲java大神。另外配置環境變量的目的是爲了告訴系統咱們的JDK安裝在了那裏,當咱們在CMD控制檯進行調用開發工具和運行工具的時候系統能夠及時找到並相應。
CMD控制檯在哪裏?咱們能夠同時按下「windows」+「R」,而後在出現的窗口中輸入cmd,而後點擊回車。就進入了cmd控制檯了。


五、開始編寫程序HolleWorld.java
開發環境已經搭建完畢,能夠開發咱們第一個Java程序了。 Java程序開發三步驟:編寫、編譯、運行。

如今咱們用記事本寫咱們第一個java程序————HolleWorld.java
咱們在本身的硬盤(D、E、F等那個盤都行)根目錄下新建一個文件夾,起名爲exercise,做爲本身之後java的練習文件夾,並在該文件夾中新建一個記事本文件。而後把.txt的拓展名修改成.java。

而後用記事本打開,編寫以下內容。

public class Hello {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
按照以下圖所示方法【在Hello.java所在的路徑下輸入cmd而後回車】進入cmd控制檯,此時的路徑就是Hello.java文件所在的路徑,而後按照如圖所示的過程輸入命令就能夠運行該java程序,輸出Hello,World!【小技巧:在控制檯輸入當前路徑下的文件名時,當輸入文件名的前幾個字母能夠使用Tab鍵進行補全。】
拓展瞭解:關於java中類的命名和文件的命名規範請查看>>java類的命名規則與規範<<【在第4講中是重點】

至此,第一個java程序編寫完成。今後你就不是小白了!歡迎進入java世界。
六、java的基本語法(1)
咱們編寫程序時通常不是隻寫代碼的,還要在代碼中添加註釋,加強代碼的可讀性,容易理解。因此上方的代碼添加註釋後就是下面這個樣式
public class Hello {
public static void main(String[] args) {
//做者:Alvin
//功能:打印輸出Hello World!
//時間:2019年2月21日
System.out.println("Hello World!");
}
}
上方代碼中,雙斜槓//後面的文字都是註釋內容,java中添加註釋的方法共有三種,分別是
①、//行註釋,我是註釋內容
②、/*
塊註釋,我是註釋內容
*/
③、/**
*文檔註釋,我是註釋內容
*/
通常註釋寫在功能代碼的上方,註釋是給人看的,不是給機器看的,機器在編譯的時候直接跳過註釋。
在使用javac命令進行編譯Hello.java文件時,javac.exe會把Hello.java文件編譯成Hello.class文件【即字節碼文件】,而後經過java命令利用java.exe解釋器執行java文件加載到虛擬機運行。
java程序在運行的時候老是從main方法開始,也就是說main方法是程序執行的入口。
說明:java程序若是運行必定是從main方法開始,main方法不必定要放在public修飾的公共類中,也就是說包含main()的類若是運行不必定要是public修飾的類。
【拓展閱讀】
《深刻jvm第二版》中有這樣一句話:java虛擬機實例經過調用某個類的main()來運行一個Java程序,而這個main()必須是public static void 並接收一個字符串數組做爲參數,任何擁有這樣一個main()的類均可以做爲java程序的起點。並無說擁有main()方法的類必定要是public類。
在java文件中,若是擁有public類,則該文件的文件名務必和該類徹底相同。
java程序在編寫的時候,java文件的文件名(如Hello.java)必定要和文件中public修飾的類類名一致,即圖中的兩個名稱一致。

不然java編譯時會出錯,沒法經過。
對於目前程序中出現的單詞解釋以下
public :表示這個類是公共的,一個java文件中只能有一個public類
class :表示這是一個類
Hello :類名(公共類的類名必須和文件名一致)
main :一個主函數,至關因而程序的入口
第2講 變量、數據類型
1、問題引入
①、首先看一個程序
/*
* 做者:Alvin
* 功能:計算兩個數的和
* 時間:2019年2月21日15:58:06
* ***********************************************
* public :表示這個類是公共的,一個java文件中只能有一個public類
* class :表示這是一個類
* Hello :類名(公共類的類名必須和文件名一致)
* main :一個主函數,至關因而程序的入口
*/
public class AddCalculate {
//一個主函數,至關於程序的入口
public static void main(String[] args) {
//定義一個變量,變量名爲a,它的值爲10
int a=10;
//定義一個變量,變量名爲b,它的值爲20
int b=20;
//定義了一個變量,變量名爲result,它的值是a+b的和
int result=a+b;
//輸出結果
System.out.println("結果是:"+result);
}
}
上方代碼執行後輸出的內容以下圖

②、而後咱們瞭解一個概念————內存。

每一個計算機中都有內寸,而計算機中的程序都必須被加載到內存中才能會被執行。因此咱們若是要讓計算機幫助咱們處理問題就必須把問題抽象成數學模型而後加載到內存中經過CPU的計算幫助咱們解決。
在本講開頭的程序中,程序中的以下語句
//定義一個變量,變量名爲a,它的值爲10
int a=10;
//定義一個變量,變量名爲b,它的值爲20
int b=20;
//定義了一個變量,變量名爲result,它的值是a+b的和
int a=10 的做用就是請求系統從內存中得到必定大小的內存空間,而且給這個內存空間起名爲a,並把10這個數字存入到內存中。同理int b=20 的做用也是這樣。
語句
//定義了一個變量,變量名爲result,它的值是a+b的和
int result=a+b;
的做用也是請求系統從內存中分配一個內存空間並起名爲result,同時把a這塊內存空間的值和b這塊內存空間的值求和獲得的數的大小存入到result而a,b這兩塊內存中的值保持不變。
因此,相似於上面這樣的語句就是請求內存並給該內存起一個別名且存儲數據到請求到的內存中。
咱們再看個程序
/*
* 做者:Alvin
* 功能:對同一塊名稱爲a的內存存儲不一樣的數值
* 時間:2019年2月21日16:40:55
* ***********************************************
* public :表示這個類是公共的,一個java文件中只能有一個public類
* class :表示這是一個類
* Hello :類名(公共類的類名必須和文件名一致)
* main :一個主函數,至關因而程序的入口
*/
public class AddCalculate {
//一個主函數,至關於程序的入口
public static void main(String[] args) {
//定義一個變量,變量名爲a,它的值爲10
int a=10;
//打印a內存中存儲的數值
System.out.println(a);
//把20存入到名字爲a的內存中
a=20;
//打印a內存中存儲的數值
System.out.println(a);
}
}
上方代碼的執行結果爲
10
20
說明內存分配給a的這塊空間是能夠存儲不一樣數值的,但若是a從新存儲了其餘數值則原來的數值會被覆蓋掉。咱們能夠直接用字母a打印出其所表示的內存中的數值,說明a能夠認爲是一個數量,而且在先後兩次數值存儲後當咱們的輸出的都是a的狀況下數值改變了,說明a是一個變化的數量,咱們稱爲變量
總結:上方在向系統申請內存的過程【即int a語句】稱爲定義變量,在向得到的內存中存儲數據的過程【即語句a=10】稱爲:賦值
2、提出問題
一、爲何有變量?
咱們應該首先知道程序是用來解決生活實際問題的,因此說程序是在現實生活中抽象出來的數學物理模型的基礎上進行編寫的。而現實生活又是一個動態的世界,不是一副靜態的畫,因此抽象的數學物理模型在表達動態的量的時候也是用變量表示的,程序員再把模型轉換成程序的時候也就必須使用變量去呈現表達,經過變化的量來表達固定的數學物理模型。
固然、在數學物理模型中還存在一些固定不變的量,像圓周率3.1415926,像普朗克常量等,這些量稱爲常量。
而且變量是任何編程語言的基本組成單位。變量是用來幫助系統在內存中分配多大的和什麼結構的內存。
二、全部變量都是同樣的麼?
不是,java基本數據類型有四大變量類型

三、爲何要分數據類型?
程序在請求系統分配內存空間的時候,爲了不形成內存浪費,保證有限的內存空間合理使用,又爲了存儲在內存中的數據安全,不容許一個變量名去取用其餘內存空間的數據,還有其餘因素,因此制定了不一樣的數據類型讓系統分配規定大小和類型的內存空間。
假如用來存儲一我的的年齡,使用int類型[如今不用明白爲何,後面會講解]的變量就能夠,而用來存儲一我的的身份證號碼使用int類型的變量就不能夠了,由於18位身份證號碼數據長度大大超過了int所能存儲的數據長度。
因此規定數據類型是爲了在系統分配內存時可以對不一樣數據類型分配請求分配不一樣大小的內存空間。
四、 不一樣數據類型在內存中分配的大小是多少?
計算機基礎知識拓展【不懂也不要緊】
咱們知道,一般
電路開關在開、合時電燈亮、滅,則電路開關能夠用0、1進行表示,而
電路開關在開、合下電燈滅、亮,則電路開關能夠用一、0,咱們稱這種電路爲非電路【電路情形開關合燈泡並聯】。
相似的還有當電路兩開關爲串聯是開關所有合上時電燈亮的電路稱爲與電路,還有當電路兩開關爲並聯時開關有一個合上時電燈亮的電路稱爲或電路等
內存中是這些成千上萬個電路相CU互結合而成。內存上有不少邏輯電路的電平輸入點,每個點的一個0或1表示一位。計算機CPU處理來自於內存中的數據。
數據在計算機中存儲是以補碼【後續會詳細說明】的方式在內存中存儲的。
計算機計算加減乘除其實都是按照加法來進行運算的。
人爲規定:在計算機中
1 Byte=8bit【即8位】
1KB=1024Byte
1MB=1024KB
1GB=1024MB
1TB=1024GB
1PB=1024TB
1EB=1024PB
在問題2中,咱們知道,java的基本數據類型有四種,分別是
整型、浮點型、布爾型、字符型
下面詳細介紹各個類型的精度(存儲大小範圍)和詳細分類
①、整型
整型變量包括四種整型類型,關於數據長度和大小範圍詳見>>內容擴展模塊①
整型類型分類
類型名 |
佔用內存大小 |
保存數據大小範圍 |
最大保存整數的長度 |
byte |
1B |
-128~127 |
3 |
short |
2B |
-12768~12767 |
5 |
int |
4B |
-2147483648~2147483647 |
10 |
long |
8B |
-9223372036854775808~9223372036854775807 |
19 |
一個整數在未加說明的狀況下系統會默認爲int類型。
②、浮點型
關於浮點數在內存中的存儲以及精確位數詳見>>內容擴充模塊③<<
浮點類型分類
類型名 |
佔用內存大小 |
保存數據大小範圍 |
最大能精確保存的長度 |
float |
4B |
-3.40E+38 ~ +3.40E+38 |
絕對保證6位,有效數字7位 |
double |
8B |
-1.79E+308 ~ +1.79E+308 |
絕對保證15位,有效數字16位 |
一個浮點數在未加說明的狀況下,系統默認爲double類型。典型案例:float f=2.3;編譯時會報錯,由於2.3是double類型不能賦值給float【緣由後面會講】,此時須要在小數後面加一個大寫或者小寫的F,即float f=2.3f;
③、布爾型
boolean型變量只有true和false兩種值。具體boolean類型的變量佔用幾個字節,沒有給出精確的定義【詳細介紹查看>>內容擴充模塊④<<】。
④、字符型
char類型只能表示一個字符,佔用1個字節。單個字符必須使用英文的單引號引發來且引號以內只能有一個字符【一個漢字或一個字母】,如 char str='a';另外字符型變量能夠同整型數值進行運算。若是表示一個字符串,如"avaljbljlajklsd",則要使用String類型,String是個類,String是引用數據類型,後續作講解。注意字符串必須使用英文雙引號進行包圍。
緣由詳見>>內容擴充模塊⑤<<
⑤、空型
最後還有一個空類型null,全部字母必須小寫。
五、關鍵字和標識符
關鍵字: Java預先定義好的,具備特殊含義的單詞(如上方的public 、static 、void 、main、return、int 、float、等)詳見>>內容擴展模塊⑩<<
標識符: 由程序員本身定義的單詞
類名,變量名,方法名
標識符的命名規則和規範
規則:
a.必須由數字,字母,_,$組成
b.數字不能開頭
c.不能和關鍵字重名
規範:
a.見名知義
b.駝峯式命名法
類名:全部單詞的首字母大寫(大駝峯)
變量名,方法名:第二個單詞開始首字母大寫(小駝峯)
關鍵字都是小寫的
稱爲大神,第二步: 嚴格遵循規則和規範
如下哪些標識符是合法的:
HelloWorld Hello_World Hello_
_Hello Hello World 1HelloWorld
六、定義變量規則與規範
格式:
數據類型 變量名 = 值; 這裏值 必須對應數據類型
變量定義中須要注意的兩點
首先要遵照標識符的命名規則與規範
a.在同一個大括號中,不能定義兩個名字同樣的變量(哪怕類型不同也不行!!!)
b.變量定義後能夠不賦值,可是該變量是不能使用
c.變量能夠先定義後賦值,並且能夠賦值屢次,以最後一次賦值爲準
d.一些奇葩的定義和賦值方式
int a,b; ab都沒有賦值
int a = 10,b; a賦值了 b沒有賦值
int a,b = 10; a沒有賦值,b賦值了
int a = 10,b = 20;ab都賦值了
可以定義8種基本數據集類型的變量
整型變量
byte/short/int/long 變量名 = 值;
在發開中若是沒有特殊說明,建議使用int
若是咱們使用的是long,須要在值後面加上L
浮點型變量
float/double 變量名 = 值;
在發開中若是沒有特殊說明,建議使用double
若是咱們使用的是float,須要在值後面加上F
字符型變量
char c = 'a';
布爾型變量
boolean b = true/false;
字符串型變量
String name = "jack";
七、如何使用上方的數據類型?
仔細閱讀並編寫下方代碼並上機運行,體會變量的定義和使用。
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
//定義一個整型變量age並賦值爲10
int age=10;
//定義一個byte短整型變量page並賦值爲10
byte page=10;
//定義一個short整型變量num並賦值爲8
short num=8;
//定義一個字符型變量並賦值'a'
char ch='a';
//定一個字符型變量並賦值'b'
char cha='b';
//定義一個boolean變量並賦值爲true
boolean bo=true;
//定義一個單精度浮點型變量
float fl=2f;
//輸出上方的結果
System.out.println(age);
System.out.println(page);
System.out.println(num);
System.out.println(ch);
System.out.println(bo);
System.out.println(fl);
//輸出字符類型和int類型的計算結果
System.out.println(cha+age);
}
}
輸出結果以下
八、數據類型的轉換
爲何會有數據轉換?由於java規定,不一樣類型的數據之間不能進行數據運算。因此須要進行數據類型轉換才能進行算術運算。數據類型的轉換包括自動轉換和強制轉換。
①、自動轉換
java在進行運算的時候在一些狀況下會進行自動轉換,其自動轉換遵循如下規則
一、byte、short、char變量在進行運算的時候自動轉爲int類型
二、boolean類型不參與任何類型的轉換
三、float變量在運算的時候會自動轉換成double變量
變量自動轉換的方向
char、byte、short(只要這三個變量參與運算就直接被系統轉換爲int)————》int————》long————》float————》double
②、強制轉換
強制轉換的做用對象是變量,強制轉換的方向是從高精度轉換成低精度,轉換後可能會丟失數據精度。通常在開發中儘可能少用。
強制類型轉換的方法
在須要進行強制類型轉換的變量前加小括號並在小括號內輸入須要轉換成的目的數據類型。
如float a=10.99;
int b=(int)a;
上方的代碼意義是將浮點型變量強制轉換成整型變量而後把值賦給b變量,轉換後的變量會丟失掉小數點後面的部分。
③、變量和常量的運算__編譯器的常量優化
int a = 3.14; //報錯的,3.14是寬類型 a是窄類型
float f = 3.14; //報錯的
a.編譯器會對常量進行優化
byte b = 10;//編譯器常量優化,只要該常量沒有超過byte的範圍,編譯器自動優化
short s = 10;//編譯器常量優化,只要該常量沒有超過short的範圍,編譯器會自動優化
b.編譯器會對常量計算進行優化
byte b = 10 + 20;//編譯器也會對常量計算後的結果優化,只要結果沒超過byte範圍
byte b1 = 10;
byte b2 = 20;
byte b = 10 + 20; //常量計算能夠優化
byte b = b1 + 20; //含有變量b1不能優化
byte b = 10 +b2; //含有變量b2不能優化
byte b = b1 + b2; //更不能優化
拓展:在java中當咱們定義一個變量後若是沒有賦值則會被系統賦予初始值,初始值結果以下
byte short int long的初始值爲0
char的初始值爲' '空格
float和double是0.0
boolean的初始值是false
引用類型是null
九、案例展現
一、交換兩個變量中保存的整數的位置【思路一:此思路會浪費內存】
public class Alvin{
public static void main(String [] args)
{
//定義兩個變量
int a=10;
int b=20;
int c=0;
//開始交換兩個數的位置
c=a;
a=b;
b=c;
System.out.println("a="+a+";b="+b);
}
}
二、交換兩個變量中保存的整數的位置【思路2:此思路有侷限,可能溢出數據】
public class Alvin{
public static void main(String [] args)
{
//定義兩個變量
int a=10;
int b=20;
//交換兩個變量的位置
a+=b;
b=a-b;
a=a-b;
System.out.println("a="+a+";b="+b);
}
}
三、交換兩個變量中保存的整數的位置【思路3:最好的方法】
public class Alvin{
public static void main(String [] args)
{
//定義兩個變量
int a=10;
int b=20;
//交換兩個變量的位置
a=a^b;
b=a^b;
a=a^b;
System.out.println("a="+a+";b="+b);
}
}
第3講 運算符和流程控制
1、運算符
一、算術運算符
算術運算符
運算符符號 |
名稱 |
功能 |
分類 |
舉例 |
+ |
加 |
將兩個操做數相加 |
雙目運算符 |
int a=3+4;//結果把7賦值給a |
— |
減 |
將兩個操做數相減 |
雙目運算符 |
int a=10-3;//結果把7賦值給a |
* |
乘 |
將兩個操做數相乘 |
雙目運算符 |
int a=3*4;//結果把12賦值給a |
/ |
除 |
將兩個操做數相除 |
雙目運算符 |
int a=29/4;//結果把7賦值給a |
% |
模 |
對第一個操做數以第二個操做數的倍數取餘 |
雙目運算符 |
int a=16%9;//結果把7賦值給a |
++ |
自加 |
將一個變量的值加1 |
單目運算符 |
int i=0; i++;【或++i】最後把i的值賦值爲1 |
—— |
自減 |
將一個變量的值減1 |
單目運算符 |
int i=2; i--;【或--i】最後把i的值賦值爲1 |
關於自加和自減的詳細講解詳見>>內容擴充模塊⑦<<
算術運算符中的*、/運算中,只要操做數中有一個浮點型變量則系統直接把另外一個操做數轉換成浮點型變量,計算結果爲浮點型。
注意事項:
①、在利用算術運算符計算中注意自動轉換。
②、在*、/、%運算中,兩個操做數若是有一個爲浮點型變量或常量則結果也爲浮點型。
③、運算結果不能夠超過左側變量的存儲範圍。
應用舉例:寫一個java程序判斷兩個數可否被整除,能則返回"能夠被整除"不然返回"不能被整除"。
下方代碼部分代碼目前無需深究,後續會有講解。
/*
* 做者:Alvin
* 功能:判斷兩個數可否被整除
* 時間:2019年2月22日08:53:05
* */
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
//定義被除數變量
int firstDivisor=10;
//定義除數變量
int secondDivisor=3;
//對餘數結果進行判斷,餘數爲零則能夠被整除,餘數不爲零則執行else語句中的字段
//下方的if...else..目前無需糾結
if(firstDivisor%secondDivisor == 0)
{
System.out.println("能夠被整除");
}
else
{
System.out.println("不能被整除");
}
}
}
二、複合賦值運算符
複合賦值運算符
運算符符號 |
名稱 |
功能 |
種類 |
舉例 |
+= |
加等於 |
對兩個操做數求和並賦值給左側變量 |
雙目運算符 |
a+=3;等價於a=a+3; |
-= |
減等於 |
對兩個操做數求差並賦值給左側變量 |
雙目運算符 |
a-=3;等價於a=a-3; |
*= |
乘等於 |
對兩個操做數求積並賦值給左側變量 |
雙目運算符 |
a*=3;等價於a=a*3; |
/= |
除等於 |
對兩個操做數求商並賦值給左側變量 |
雙目運算符 |
a/=3;等價於a=a/3; |
%= |
模等於 |
對兩個操做數求餘並賦值給左側變量 |
雙目運算符 |
a%=3;等價於a=a%3; |
三、關係運算符
關係運算符中其計算結果是boolean型,結果只有true或false。
關係運算符
運算符符號 |
名稱 |
功能 |
種類 |
舉例 |
舉例中運算返回結果 |
> |
大於 |
判斷左側的值是否大於右側的值 |
雙目運算符 |
10>20 |
false |
< |
小於 |
判斷左側的值是否小於右側的值 |
雙目運算符 |
10<20 |
true |
>= |
大於等於 |
判斷左側的值是否大於或等於右側的值 |
雙目運算符 |
10>=20 |
false |
== |
等於 |
判斷左側的值是否等於右側的值 |
雙目運算符 |
10==20 |
false |
<= |
小於等於 |
判斷左側的值是否小於或等於右側的值 |
雙目運算符 |
10<=20 |
true |
!= |
不等於 |
判斷左側的值是否不等於於右側的值 |
雙目運算符 |
10!=20 |
true |
四、邏輯運算符
邏輯運算符
運算符符號 |
名稱 |
功能 |
種類 |
舉例 |
舉例中運算結果 |
&& |
邏輯與【又稱短路與】 |
根據運算符兩側的boolean值判斷真假 |
雙目運算符 |
5>4 && 1<0 |
false |
|| |
邏輯或【又稱短路或】 |
根據運算符兩側的boolean值判斷真假 |
雙目運算符 |
5>4 && 1<0 |
true |
! |
非 |
對原來的boolean值取反 |
單目運算符 |
!(5>4 && 1<0) |
false |
^ |
異或 |
根據運算符兩側的boolean值判斷真假 |
雙目運算符 |
5>4 ^ 1<0 |
true |
邏輯運算符的真假狀況以下
①、全部的邏輯運算符,只能運算布爾類型的數據
②、&& 短路與 規則:全真爲真 一假則假
③、|| 短路或 規則:全假爲假 一真則真
④、^異或 規則:相同爲假 不一樣爲真
⑤、! 非,取反 注意:只能操做一個布爾值 規則:真變假 假變真
五、三目運算符
三目運算符的結構
流程是:若是布爾表達式的判斷爲true則返回值1,不然返回值2.
案例
// 使用三目運算符求兩個數的較大者
int a=10,b=20;
System.out.println(a>b?a:b);
輸出結果
六、補充
運算符是有優先級的,他們的優先級順序以下圖所示

總結:從上方的表能夠看出:小括號、方括號、成員訪問符(.) > 單目運算符 > 算術運算符 > 移位運算符 > 邏輯運算符 > 邏輯運算符(==和!=) >&>|>&&>||>三目運算符>複合賦值運算符
2、流程控制
顧名思義,流程控制就是對程序執行的流程進行控制,也就是控制程序語句何時,什麼狀況下執行那一條語句。根據分類的不一樣,流程控制能夠分爲順序控制、分支控制和循環控制
一、順序控制
順序控制就是程序按照從上而下,從左而右的順序順序執行。
以下程序
/*
* 做者:Alvin
* 功能:流程控制之順序控制
* 時間:2019年2月22日13:42:42
* */
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("我是第1句");
System.out.println("我是第2句");
System.out.println("我是第3句");
System.out.println("我是第4句");
System.out.println("我是第5句");
System.out.println("我是第6句");
}
}
程序執行後的結果輸出以下

結果說明,在正常狀況下,程序會按照代碼的書寫順序執行。
可是事實上,咱們在生活中總須要進行選擇,有了選擇就有了在某種狀況下咱們須要作什麼事,程序也是如此。
二、分支控制
分支控制就是在某種狀況下指定程序執行那些代碼塊。
分支控制中有如下幾個分類:
一、單分支
單分支語句就像生活中一件事咱們作或不作。
語句格式:
if(關係表達式或boolean類型變量或非0值)
{
須要執行的代碼塊;
}
案例:
/*
* 做者:Alvin
* 功能:單分支語句案例
* 時間:2019年2月22日13:51:57
* */
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
//定義兩個變量
int a=2;
//此處的判斷條件爲boolean表達式
//第一個單分支語句
if(a==2);
{
//當知足a==2時,把a的值更改成3
a=3;
//輸出a的值
System.out.println("a的值爲:"+a);
}
//第二個單分支語句
if(a!=2);
{
//當知足a!=2時,把a的值更改成2
a=2;
//輸出a的值
System.out.println("a的值爲:"+a);
}
if(a==1);
{
//當知足a==1時,把a的值更改成2
a=2; //輸出a的值 System.out.println("a的值爲:"+a); }
} }
上方代碼輸出結果以下

說明當if()中小括號中的boolean表達式(或者其餘值)爲真(或者不爲0)時,if語句後面的大括號中的代碼塊會被執行,當小括號中的boolean爲假false時,if後面的大括號中的代碼塊不會被執行
二、雙分支
雙分支就是對一件事進行判斷,假如成立時幹什麼,假如不成立時又幹什麼。
語句格式以下
if(boolean表達式);
{
//若是boolean表達式成立則執行該括號裏面的代碼塊
代碼塊;
}
else
{
//若是boolean表達式中不成立,即爲false,則執行下方代碼塊
代碼塊;
}
案例:
/*
* 做者:Alvin
* 功能:雙分支語句案例
* 時間:2019年2月22日14:09:16
* */
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
/*
*要求: 拋硬幣,用0表示背面,用1表示正面,若是拋出的硬幣爲正面則學貓叫,不然學狗叫
* */
//定義一個變量result表示拋硬幣獲得的結果,咱們假設硬幣的結果爲1
int result=1;
if(result==0)
{
//若是結果爲背面學狗叫
System.out.println("汪汪!");
}else
{
//若是結果爲正面學貓叫
System.out.println("喵喵!");
}
}
}
運行結果
修改result=0後輸出的結果
但願本身調試體會。
注意:if語句中的else會根據就近原則向上匹配最近的if。
三、多分支
多分支語句是用來應對多種狀況下的語句選擇。
多分支語句有兩種,分別爲
一、if型
if(boolean表達式)
{
代碼塊;
}
else if(boolean表達式)
{
代碼塊;
}
else if(boolean表達式)
{
代碼塊;
}
...........//此處省略n個else if
else{
代碼塊;
}
2 、switch....case .....case....default
switch(變量值)
{
case 常量1:語句;break;
case 常量2:語句;break;
case 常量3:語句;break;
case 常量4:語句;break;
case 常量5:語句;break;
................//此處省略n個case語句
default:語句;break;
}
先執行表達式,獲取值
表達式的值和case後面的值依次作匹配
哪個case後面的值與表達式的值相等,那麼就執行其後面的代碼塊
執行完畢代碼塊後,因爲有一個break,那麼整個switch結束
若是全部的值都沒有匹配上,那麼無條件執行default後面的代碼塊
關於switch....case.....default語句須要注意的幾點。
①、能夠使用的數據類型主要有byte、short、int、char、enum、String(JDK7中新增的)等。內容擴充文章<<
②、default語句寫在switch代碼塊中的判斷位置不影響最後的執行結果。if語句若是包含else則else必不能提早寫。java語言中的else遵循向上就近匹配原則,即else會匹配其上方最近的的一個if或else..if結構。
③、case後面只能是常量,且case後面的常量不能有相同的常量。
④、在switch的每一個分支斷定語句中,break做爲每條分支語句執行成功後switch結構的終結,若是省略會發生case穿透,則程序對下方case再也不判斷,而是直接執行,知道碰見一個break或者switch語句執行完畢爲止。
⑤、switch和if....else語句中的default和else可做爲全部條件都沒法匹配後的保守處理結果,可在開發過程當中下降程序的bug帶來的影響。
代碼演示【主要代碼】
int a=10,b=20;
System.out.println(a>b?a:b);
switch(a)
{
default:System.out.println("default");break;
case 10:System.out.println("10");
case 5:System.out.println("5");
case 6:System.out.println("6");break;
case 7:System.out.println("7");break;
case 20:System.out.println("20");break;
}
輸出結果
從上方能夠看出,default的位置與結果無關,缺乏break的語句會發生case穿透【即忽視下方case斷定的存在】直接執行case後面的代碼塊。
三、循環控制
循環控制流程包括三種
一、for
首先看一下for循環的結構
for(①初始化語句;②循環條件;④自增(減)循環計數器){
③循環體
}
執行流程:
首先執行①--> ②③④ --> ②③④ --> ...........-->②結束
看一個案例
//在控制檯輸出7次Hollow World
//使用for循環
for(int i = 0;i < 7; i++){
System.out.println("Hello World!");
}
執行結果
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
使用for循環的建議
①、for循環中,循環計數器的初始值建議從0或者1開始
②、for循環中定義的變量不能夠超越for循環大括號所包圍的範圍。
二、while
格式:
①初始化語句
while(②循環條件){
③循環體;
④自增(減)循環計數器
}
執行流程:
首先執行①--->②③④-->②③④---> ...--->②
案例
//在控制檯輸出7次Hollow World
//使用while循環
int i = 0;
while(i < 7){
System.out.println("Hello World!");
i++;
}
輸出結果
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
拓展:while循環在不肯定循環次數的程序中比較經常使用,代碼樣式更簡潔
以下案例
/*
* 做者:Alvin
* 功能:猜數字--while循環實現
* 時間:2019年2月22日20:27:04
* */
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Test2 {
public static void main(String[] args){
// TODO Auto-generated method stub
//建立對象,接收從鍵盤輸入的數據
InputStreamReader isr=new InputStreamReader(System.in);
BufferedReader bf=new BufferedReader(isr);
//循環設置爲永真條件,用於在猜錯的時候進行再次生成隨機數,循環猜數
while(true){
//打印提示,提示用戶輸入
System.out.println("輸入你要猜的數【0-9】:");
//try.....catch用於捕捉輸入流的異常
try {
//對輸入的數和系統隨機生成的數進行判斷是否相等,若是相等則執行下方代碼塊
if(Integer.parseInt(bf.readLine()) == (int)(Math.random()*10))
{
System.out.println("你好棒!猜對了!是你最懂個人心!");
break;
}
//若是系統產生的隨機值和本身的數值不一致,則輸出提示繼續猜。
else
System.out.println("很差意思接着猜!");
} catch (Exception e) {
System.out.println("可能輸入有誤!");
}
}
}
}
執行結果演示
輸入你要猜的數【0-9】:
5
很差意思接着猜!
輸入你要猜的數【0-9】:
4
很差意思接着猜!
輸入你要猜的數【0-9】:
6
你好棒!猜對了!是你最懂個人心!
上方程序也能夠用for循環進行改寫以下
/*
* 做者:Alvin
* 功能:猜數字--for循環實現
* 時間:2019年2月22日20:31:33
* */
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Test2 {
public static void main(String[] args){
// TODO Auto-generated method stub
//建立對象,接收從鍵盤輸入的數據
InputStreamReader isr=new InputStreamReader(System.in);
BufferedReader bf=new BufferedReader(isr);
//循環設置爲永真條件,用於在猜錯的時候進行再次生成隨機數,循環猜數
for(;true;){
//打印提示,提示用戶輸入
System.out.println("輸入你要猜的數【0-9】:");
//try.....catch用於捕捉輸入流的異常
try {
//對輸入的數和系統隨機生成的數進行判斷是否相等,若是相等則執行下方代碼塊
if(Integer.parseInt(bf.readLine()) == (int)(Math.random()*10))
{
System.out.println("你好棒!猜對了!是你最懂個人心!");
break;
}
//若是系統產生的隨機值和本身的數值不一致,則輸出提示繼續猜。
else
System.out.println("很差意思接着猜!");
} catch (Exception e) {
System.out.println("可能輸入有誤!");
}
}
}
}
三、do....while
格式:
①初始化語句
do{
③循環體
④步進語句
}while(②循環條件);
執行流程:
首先執行①③④-->②③④-->②③④-->.....--->②結束
特色:do..while循環是先執行一次,再去判斷
至少會執行一次
案例:把上方的猜數字再用do....while改寫一下,代碼以下
/*
* 做者:Alvin
* 功能:猜數字--do.....while循環實現
* 時間:2019年2月22日20:36:54
* */
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Test2 {
public static void main(String[] args){
// TODO Auto-generated method stub
//建立對象,接收從鍵盤輸入的數據
InputStreamReader isr=new InputStreamReader(System.in);
BufferedReader bf=new BufferedReader(isr);
//循環設置爲永真條件,用於在猜錯的時候進行再次生成隨機數,循環猜數
do{
//打印提示,提示用戶輸入
System.out.println("輸入你要猜的數【0-9】:");
//try.....catch用於捕捉輸入流的異常
try {
//對輸入的數和系統隨機生成的數進行判斷是否相等,若是相等則執行下方代碼塊
if(Integer.parseInt(bf.readLine()) == (int)(Math.random()*10))
{
System.out.println("你好棒!猜對了!是你最懂個人心!");
break;
}
//若是系統產生的隨機值和本身的數值不一致,則輸出提示繼續猜。
else
System.out.println("很差意思接着猜!");
} catch (Exception e) {
System.out.println("可能輸入有誤!");
}
}while(true);
}
}
沒用的知識點擴充:
死循環: 永不中止的循環
Java中最簡單的死循環:
while(true);
for(;;);
java中循環上只有while和for循環能夠省略掉花括號。
四、跳轉控制語句
轉移控制語句是當程序運行遇到下列關鍵字時須要進行跳轉。常見類型以下
一、break;在循環代碼塊中用於當即結束其所在層的循環,繼續執行下方代碼。
二、continue;在循環代碼中當即結束剩餘循環代碼塊直接跳轉到下一次循環。
三、return;當程序遇到return時會當即結束包含該語句的整個方法,繼續執行該方法後的下方代碼,在每一個方法內部,return後面不能再寫無效代碼
四、System.exit;當程序遇到該語句時,直接跳轉到改程序的結束位置,程序結束。
TIp:
1.break的介紹舉例
break語句的做用:馬上立刻rightnow結束整個循環
public static void main(String[] args) {
// TODO Auto-generated method stub
// 打印1-10
for (int i = 1; i < 11; i++) {
// 判斷
if (i == 3) {
break; // 打斷
}
System.out.println(i);// 1 2
}
System.out.println("循環結束了..");
}
執行結果
2.continue的介紹
continue語句的做用:馬上立刻結束本次循環,繼續下一次循環
public static void main(String[] args) {
// TODO Auto-generated method stub
// 打印1-10
for (int i = 1; i < 11; i++) {
// 判斷
if (i == 3) {
continue; // 跳過本次
}
System.out.println(i);// 1 2 4 5 6 7 8 9 10
}
System.out.println("循環結束了..");
}
輸出結果
1
2
4
5
6
7
8
9
10
循環結束了..
必須明確的事情是:break和continue只對循環有效,對if判斷語句無效。
五、補充
一、拓展視野
上方的循環語句已經講解完畢,須要補充的是在編程過程當中常用的嵌套循環結構,也就是在循環結構中又包含了若干個循環結構。像下方這樣的爲兩層for循環
//.嵌套循環:
// 一個循環的循環體是另一個循環
// 格式:
for(初始化語句;循環條件;步進語句){
for(初始化語句;循環條件;步進語句){
循環體;
}
}
在開發中,嵌套幾層循環【不必定是for循環】根據實際狀況而定。
二、Java入門程序聯繫
一、打印10次Hello【源碼以下】
public static void main(String[] args) {
// TODO Auto-generated method stub
for(int i=0;i<10;i++)
{
System.out.println("Hello");
}
}
二、打印100個1【源碼以下】
public static void main(String[] args) {
// TODO Auto-generated method stub
for(int i=0;i<100;i++)
{
System.out.println(1);
}
}
三、打印從1~100【源碼以下】
public static void main(String[] args) {
// TODO Auto-generated method stub
for(int i=1;i<=100;i++)
{
System.out.println(i);
}
}
四、打印1~100之中的偶數【源碼以下】
public static void main(String[] args) {
// TODO Auto-generated method stub
for(int i=1;i<=100;i++)
{
if(i%2==0)
System.out.println(i);
}
}
五、打印從1~100之中的素數【源碼以下】
public static void main(String[] args) {
// TODO Auto-generated method stub
//外層循環生成須要斷定的數
for(int i=1;i<=100;i++)
{
//從2開始到給定的數進行逐求餘
int j=2;
while(j<=i)
{
//當餘數爲0時終止比較
if(i%j==0)
break;
j++;
}
//假如最後結果是j與i的值相同,則說明只能被其自己整除,則該數是素數
if(i==j)
System.out.println(i);
}
}
六、打印50個斐波那契數列【源碼以下】
public static void main(String[] args) {
// TODO Auto-generated method stub
//打印50個斐波那契數列
long sum=0;
long fron=0;
long back=1;
for(int k=1;k<50;k++)
{
//第一次打印把第一個1打印出來
if(k==1)
System.out.println(1);
//sum的值定義爲相鄰的前面兩個數一前一後的和
sum=fron+back;
System.out.println(sum);
//打印後兩個值向後移動一位
fron=back;
back=sum;
}
}
七、打印一個6行10列的矩形【源碼以下】
public static void main(String[] args) {
// TODO Auto-generated method stub
//外層循環控制行數
for(int line=1;line<=6;line++)
{
//內層循環控制列數
for(int row=1;row<=10;row++)
{
//打印不換行
System.out.print("*");
}
//一行打印完畢後換行
System.out.println();
}
}
八、打印一個7層的直角三角形【源碼以下】
public static void main(String[] args) {
// TODO Auto-generated method stub
//外層循環控制行數
for(int line=1;line<=6;line++)
{
//內層循環控制列數,此時注意行數與每行中星星個數的關係
for(int row=1;row<=line;row++)
{
//打印不換行
System.out.print("*");
}
//一行打印完畢後換行
System.out.println();
}
}
九、打印一個7層的等邊三角形【源碼以下】
public static void main(String[] args) {
// TODO Auto-generated method stub
//外層循環控制行數
for(int line=1;line<=7;line++)
{
//由於每行要打印兩種元素空格和星星,因此須要兩個for循環完成行內元素打印
//本循環打印空格
for(int space=1;space<=7-line;space++)
{
//打印空格不換行,數量關係與直角三角形作對比
System.out.print(" ");
}
//此時注意行數與每行中星星個數的關係
for(int row=1;row<=2*line-1;row++)
{
//打印星星不換行
System.out.print("*");
}
//一行打印完畢後換行
System.out.println();
}
}
十、打印一個實心菱形【源碼以下】
System.out.println("======菱形【在打印等腰三角形上的改變】===========");
public static void main(String[] args) {
// TODO Auto-generated method stub
//總打印行數
int rowsDia = 15;
//用於改變菱形上下打印的過分變量
int midRow=0;
for(int row=1;row<=rowsDia;row++){
//改變菱形上下打印的方向判斷
if(row<=((rowsDia+1)/2))
//當row<=(rowsDia+1)/2時,打印上半部分的賦值方法
midRow=row;
else
//當row>(rows+1)/2時,打印下半部分的賦值方式
midRow=rowsDia-row+1;
//打印空格[後面有個-3是爲了使菱形左右移動的]
for(int space=0;space<((rowsDia+1)/2)-midRow;space++)
System.out.print(" ");
//打印星星
for(int star=1;star<=2*midRow-1;star++)
System.out.print("*");
System.out.println();
}
}
十一、打印一個空心菱形【源碼以下】
System.out.println("======空心菱形【在打印菱形上的改變】===========");
//總打印行數
int rowsDiaEmpty = 15;
//用於改變菱形上下打印的過分變量
int midRowEmpty=0;
for(int row=1;row<=rowsDiaEmpty;row++){
//改變菱形上下打印的方向判斷
if(row<=((rowsDiaEmpty+1)/2))
//當row<=(rowsDiaEmpty+1)/2時,打印上半部分的賦值方法
midRowEmpty=row;
else
//當row>(rows+1)/2時,打印下半部分的賦值方式
midRowEmpty=rowsDiaEmpty-row+1;
//打印空格[後面有個-3是爲了使菱形左右移動的]
for(int space=0;space<((rowsDiaEmpty+1)/2)-midRowEmpty;space++)
System.out.print(" ");
//打印星星
System.out.print("*");
for(int star=1;star<=2*midRowEmpty-2;star++)
if(star== 2*midRowEmpty-2)
System.out.print("*");
else
System.out.print(" ");
System.out.println();
}
十二、打印一個水仙花數【源碼以下】
1三、打印九九乘法表【源碼以下】
for(int i=1;i<=9;i++)
{
for(int j=1;j<=i;j++) {
System.out.print(j+"x"+i+"="+(i*j)+"\t");
}
System.out.println();
}
拓展:java語句在控制檯的格式化輸出
double d = 12.345;
String s = "TestString!";
int i = 1234;
//"%"表示進行格式化輸出,"%"以後的內容爲格式的定義。
System.out.printf("%f\n",d);//"f"表示格式化輸出浮點數。 後面的\n表示換行
System.out.printf("%6.2f\n",d);//"6.2"中的6表示輸出的長度,2表示小數點後的位數,符合四捨五入,默認右對齊。
System.out.printf("%+9.2f\n",d);//"+"表示輸出的數帶正負號。
System.out.printf("%-9.4f\n",d);//"-"表示輸出的數左對齊(默認爲右對齊)。
System.out.printf("%+-9.3f\n",d);//"+-"表示輸出的數帶正負號且左對齊。
System.out.printf("%d\n",i);//"d"表示輸出十進制整數。
System.out.printf("%o\n",i);//"o"表示輸出八進制整數。
System.out.printf("%x\n",i);//"d"表示輸出十六進制整數。
System.out.printf("%#x\n",i);//"d"表示輸出帶有十六進制標誌的整數。
System.out.printf("%s\n",s);//"d"表示輸出字符串。
System.out.printf("輸出一個浮點數:%f,一個整數:%d,一個字符串:%s\n",d,i,s);//能夠輸出多個變量,注意順序。
System.out.printf("字符串:%2$s,%1$d的十六進制數:%1$#x\n",i,s);//"X$"表示第幾個變量。
上方代碼執行結果:以0x開始的數據表示16進制,計算機中每位的權爲16,即(16進制)10 = (10進制)1×16備註:這裏的0是數字0,不是字母O!
12.345000
12.35
+12.35
12.3450
+12.345
1234
2322
4d2
0x4d2
TestString!
輸出一個浮點數:12.345000,一個整數:1234,一個字符串:TestString!
字符串:TestString!,1234的十六進制數:0x4d2
第4講 類、對象、成員方法
從本講咱們開始講解Java面向對象編程之類與對象。在類中涉及的東西特別多,如封裝、繼承、多態、實現、成員變量、成員屬性、成員函數、成員方法、構造方法、默認方法等。在內存中程序在運行的過程劃分爲代碼區、靜態區、棧區等。
本講內容:
一、java面向對象編程(1)--類與對象
二、java面向對象編程(1)--構造方法
學習目標:
1.初步掌握java中的類和對象
二、什麼是成員變量和成員方法
三、掌握構造方法的使用
1、問題引入
張老太養了兩隻貓,一隻名字叫小白,今年3歲,白色。還有一直名字叫小花,今年10歲,花色。請編寫一個程序,當用戶輸入小貓的名字時就顯示該貓的名字、年齡、顏色。若是用戶輸入的小貓名錯誤,則顯示張老太沒有
這隻貓。
咱們發現,若是用咱們現有的知識是能夠解決這個問題的。代碼以下
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/*
* 做者:Alvin
* 功能:張老太養貓案例
* 時間:2019年2月23日09:25:39
*
* */
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
//接收從鍵盤輸入的內容
BufferedReader bf=new BufferedReader(new InputStreamReader(System.in));
//定義字符串型變量name,用於存放從鍵盤接收的用戶輸入的信息
String name=null;
//try....catch異常處理
try {
//提示輸入
System.out.println("請輸入你要查詢的貓的名字:");
//從鍵盤接收一行
name=bf.readLine();
} catch (IOException e) {
// TODO Auto-generated catch block
//若是輸入不合法字段將顯示該信息提示用戶
System.out.println("對不起,您的輸入有誤,請從新輸入!");
}
//定義張老太的白貓
String cat_white="小白";
int age_white=3;
String color_white="白";
//定義張老太的花貓
String cat_hua="小花";
int age_hua=10;
String color_hua="花";
//不能夠使用==號進行判斷,由於==比較的是地址是否相同
if(cat_white.equals(name))
{
System.out.println("名字是"+cat_white+"年齡是"+age_white+"顏色是"+color_white);
}
else if(cat_hua.equals(name))
{
System.out.println("名字是"+cat_hua+"年齡是"+age_hua+"顏色是"+color_hua);
}else {
System.out.println("張老太沒有這隻貓!");
}
}
}
運行結果
請輸入你要查詢的貓的名字:
小白
名字是小白年齡是3顏色是白色
2、解決方案
可是咱們發現,上面咱們須要爲兩隻貓分別添加名字、年齡、顏色屬性,若是張老太有1萬隻貓,咱們豈不是要重複定義屬性代碼1萬次。這兩隻貓都有這三個共同的屬性,只是這三個屬性的值不一樣而已,上面的代碼書寫過於分散,那爲了使代碼看起來更簡潔統一而且提升代碼的複用率,咱們想到可不能夠把貓的共同屬性抽象出來,把共同屬性放在一塊而後給他起個名字叫貓?這樣咱們就能夠在定義變量的時候直接定義一個貓,而後給這個貓起個名字,加個年齡,配上花色就行了。這就像在天然界同樣每一個動物當他們出生的時候就已經具備其餘同類的物種所具備的共同屬性,可是他們的屬性值不一樣而已,如一隻熊貓降生時咱們就把稱之爲熊貓,而後給他起個名字叫亮亮,它還有體重,身高等和其餘熊貓共有的屬性,但屬性的值不一樣。
按照咱們剛纔的思路,咱們如今把貓這個類給抽象出來,本題咱們只需抽象出包含名字、年齡和顏色屬性的貓類。
代碼以下
/*
* 做者:Alvin
* 功能:定義一個貓類
* 時間:2019年2月23日09:53:57
*
* */
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
//定義一個貓類
class Cat
{
//貓的年齡
int age;
//貓的名字
String name;
//貓的顏色
String color;
}
若是咱們用上面的思路來寫代碼的話代碼以下
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/*
* 做者:Alvin
* 功能:定義一個貓類並實現查詢貓操做
* 時間:2019年2月23日10:35:02
*
* */
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
//第一隻貓,此時的Cat類就至關於數據類型,像定義int同樣進行定義
Cat cat1=new Cat();
cat1.name="小白";
cat1.age=3;
cat1.color="白色";
//第二隻貓
Cat cat2=new Cat();
cat2.name="小花";
cat2.age=10;
cat2.color="花色";
//接收從鍵盤輸入的內容
BufferedReader bf=new BufferedReader(new InputStreamReader(System.in));
String name=null;
//try....catch異常處理
try {
//提示輸入
System.out.println("請輸入你要查詢的貓的名字:");
//從鍵盤接收一行
name=bf.readLine();
} catch (IOException e) {
// TODO Auto-generated catch block
//若是輸入不合法字段將顯示該信息提示用戶
System.out.println("對不起,您的輸入有誤,請從新輸入!");
}
//不能夠使用==號進行判斷,由於==比較的是地址是否相同
if(cat1.name.equals(name))
{
System.out.println("名字是"+cat1.name+"年齡是"+cat1.age+"顏色是"+cat1.color);
}
else if(cat2.name.equals(name))
{
System.out.println("名字是"+cat2.name+"年齡是"+cat2.age+"顏色是"+cat2.color);
}else {
System.out.println("張老太沒有這隻貓!");
}
}
}
// 定義一個貓類
class Cat
{
// 貓的年齡
int age;
// 貓的名字
String name;
// 貓的顏色
String color;
}
運行結果
請輸入你要查詢的貓的名字:
小白
名字是小白年齡是3顏色是白色
咱們看到,這種方法也解決了問題。可是這種方法只需咱們定義兩個Cat類型的對象,更符合認識事物的思惟。
上方案例說明,咱們的方法是可行的。代碼中Cat cat1=new Cat()這句話咱們稱爲用Cat類建立一個對象cat1並對cat1進行實例化簡稱實例化一個對象。咱們上面的代碼就是面向對象的編程,簡單且不恰當的說就是對象進行操做的編程。
編程語言的發展朝向着接近人的思惟方式演變。首先由彙編語言【面向機器】,在彙編語言中,用助記符(Mnemonics)代替機器指令的操做碼,用地址符號(Symbol)或標號(Label)代替指令或操做數的地址,編程十份繁瑣。後來程序發展到面向過程的語言,如C語言,面向過程的語言也稱爲結構化程序設計語言,是高級語言的一種。在面向過程程序設計中,問題被看做一系列須要完成的任務,函數則用於完成這些任務,解決問題的焦點集中於函數。機器語言程序之因此極其複雜和晦澀難懂,一是用二進制數表示機器指令的操做碼和存放操做數的存儲單元地址。二是每一條機器指令只能執行簡單運算。面向過程語言要達到簡化程序設計過程的目的,須要作到:一是使語句的格式儘可能接近天然語言的格式:二是可以用一條語句描述完成天然表達式運算過程的步驟。所以,語句的格式和描述運算過程步驟的方法與天然表達式接近是面向過程語言的一大特點。面向對象語言(Object-Oriented Language)是一類以對象做爲基本程序結構單位的程序設計語言,指用於描述的設計是以對象爲核心,而對象是程序運行時刻的基本成分。語言中提供了類、繼承等成分,有識認性、多態性、類別性和繼承性四個主要特色。
上方案例中的類距離一個完整的類還很遙遠,上面的類是一個簡單的類。
3、案例總結
根據上方案例,咱們總結一下,類和對象的關係和區別。
①、類是抽象的,概念的,表明一類事物,好比人類,貓類
②、對象是具體的,實際的,表明一個具體事物
③、類是對象的模板,經過類能夠聲明一個具體的對象實例。
4、類-如何定義
一個全面的類定義比較複雜,以下是一個比較完整的類的結構
/*下方是一個完整的類。目前沒有學習到的地方能夠提早了解一下,之後學習能夠有印象。特別提醒一個java中能夠有好多類但只能有一個公共類。
* 一、package:該關鍵字後面是該類在src目錄下所在的路徑,該關鍵字在一個java文件中只能出現一次而且位於java文件的有效代碼的第一句
* 二、包名:包名是當前編輯的java文件在src目錄下的路徑,windows文件路徑分級是採用右斜線的,如路徑C:\Program Files\Java,而在開發工具中是以英文符號 . 進行區分文件夾上下級,符號的左側是上級文件夾,右側是下級文件夾
* 三、訪問權限修飾符:該修飾符是用來講明該類能夠被哪些類調用,當前咱們學習的只有public修飾符,它也是權限最大的修飾符,容許任何類調用該類
* 四、class:class是定義類的關鍵字,該關鍵字的字母都不能夠小寫,java是強類型語言【即嚴格區分變量類型和字母大小寫】,後面緊跟類名
* 五、類名:類名是標識符,命名規則遵循大駝峯命名法,遵照>>>>標識符的命名規則與規範<<<<。
* 六、extends:是定義類的關鍵字,該關鍵字的字母都不能夠小寫,java是強類型語言【即嚴格區分變量類型和字母大小寫】,後面緊跟父類名[在java中有些類是能夠被繼承的,就像兒子和父親的關係,兒子能夠繼承父親遺傳的屬性,如臉型,鼻型和謝頂等]
* 七、父類名:父類名就是該類須要繼承的父類的名字,extends後面緊跟父類名,可是當且僅當只能跟一個父類名
* 八、implements:是定義類的關鍵字,該關鍵字的字母都不能夠小寫,java是強類型語言【即嚴格區分變量類型和字母大小寫】,後面緊跟須要實現的接口
* 九、接口名:接口名是須要實現的接口,什麼是接口?用現實生活中的USB接口爲例,接口只提供一個接入的端口途徑,具體USB接口要實現什麼功能根據插入的設備來決定,如咱們插入一個U盤就能夠存儲,插入一個攝像頭就能夠攝像,因此也說明接口的具體實現取決於使用接口的設備。程序中的接口與此大體相同,接口的實現和繼承類不一樣,一個類能夠同時實現多個接口,也就是在implements後面能夠跟多個接口名,用英文逗號分開
* 十、成員變量:成員變量如:int a;中a就是成員變量,類中的成員變量能夠是簡單類型,也能夠是引用類型。
* 十一、成員方法:後續講解
* 十二、構造方法:後續解釋
* */
package 包名;
訪問權限修飾符 class 類名 extends 父類名 implements 接口名 {
構造方法;
成員變量;
成員方法;
}
咱們會逐步講解學習,不斷豐富類,直到面向對象編程講解完畢咱們就可以認識一個完整的類。
剛纔咱們定義了一個貓類,貓類的格式以下
上方是咱們當前認識類的一個層次。
出處出現了兩個新詞,成員變量和引用類型那什麼是成員變量呢?
成員變量是類的基本組成部分,通常是基本數據類型,也能夠是引用類型,好比咱們定義的貓類的int age;就是成員變量.
引用類型簡單的說就是指向了另外一個類,在C++中的解釋是指向了一個地址。引用類型舉例以下
/*
* 做者:Alvin
* 功能:引用類型講解
* 時間:2019年2月23日11:37:17
* */
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
//這裏的Master就是一個引用類型
Master mast;
}
}
// 定義一個主人類
class Master
{
int age;
String name;
}
複習:一個Java源文件中最多隻能有一個public類,當有一個public類時,源文件名必
須與之一致,不然沒法編譯,若是源文件中沒有一個public類,則文件名與類中沒有一致性要求。
至於main()不是必需要放在public類中才能運行程序。
咱們對於類的認識階段以下圖所示

5、建立一個對象
一、對象的建立
建立對象有兩種方法一種是先聲明在實例化【至關於對變量進行賦值】對象,另外一種是一步到位。
①、先聲明再實例化
結構
例如
//這裏的Master是一個類
Master mast;
mast=new Master();
②、聲明的同時實例化對象
結構
例如
//這裏的Master是一個類
Master mast=new Master();
二、對象訪問(使用)成員變量
通常狀況下結構【目前認知內是能夠的隨着學習的深刻,此種方式會有侷限性】
例如
//經過該語句能夠訪問主人的名字
mast.name
三、拓展[瞭解]:內存與對象
1.Java關鍵字new是一個運算符。
2.建立一個Java對象須要三部:聲明引用變量、實例化、初始化對象實例。
3.實例化:就是「建立一個Java對象」-----分配內存並返回指向該內存的引用。
4.初始化:就是調用構造方法,對類的實例數據賦初值。
5.Java對象內存佈局:包括對象頭和實例數據。以下圖:

對象頭:它主要包括對象自身的運行行元數據,好比哈希碼、GC分代年齡、鎖狀態標誌等;同時還包含一個類型指針,指向類元數據,代表該對象所屬的類型。
實例數據:它是對象真正存儲的有效信息,包括程序代碼中定義的各類類型的字段(包括從父類繼承下來的和自己擁有的字段)。
在hotSpot虛擬機中,對象在內存中的佈局能夠分紅對象頭、實例數據、對齊填充三部分。對齊填充:它不是必要存在的,僅僅起着佔位符的做用。
6.Object obj = new Object();
那「Object obj」這部分的語義將會反映到Java棧的本地變量表中,做爲一個reference類型數據出現。而「new Object()」這部分的語義將會反映到Java堆中,造成一塊存儲了Object類型全部實例數據值(Instance Data,對象中各個實例字段的數據)的結構化內存,根據具體類型以及虛擬機實現的對象內存佈局(Object Memory Layout)的不一樣,這塊內存的長度是不固定的。另外,在Java堆中還必須包含能查找到此對象類型數據(如對象類型、父類、實現的接口、方法等)的地址信息,這些類型數據則存儲在方法區中。
四、對象對象賦值
/*
* 做者:Alvin
* 功能:實現對象與對象的賦值
* 時間:2019年2月23日13:42:47
* */
public class Test {
public static void main(String []args) {
//定義第一個貓
Cat cat1=new Cat();
cat1.age=10;
cat1.name="小白";
//定義第二個貓
Cat cat2;
cat2=cat1;
System.out.println(cat2.age+cat2.name);
//經過cat2修改貓的名字
cat2.name="小花";
System.out.println(cat1.name);
System.out.println(cat2.name);
}
}
class Cat{
String name;
int age;
}
輸出結果
經過以上案例,咱們能夠這麼不許確的理解:對象中保存的實際上是指向實例數據的引用[即指針]。
6、類的成員方法
一、成員方法的定義
像咱們人不但具備身高、體重、膚色等屬性,還具備一些行爲,如說話,會跑會跳等,這就對應了咱們類中的成員方法。
下面咱們先定義一我的類,包含膚色、體重和身高。
class Person{
String color;
int weight;
float height;
}
既然人有行動,因此咱們爲人類添加一個說話的方法,在添加以前,咱們先看一下方法的格式
/*首先明確方法的位置只能是類的內部,其餘方法的外部,任何一個方法的內部都不能定義其餘方法,但一個方法內能夠調用另外一個方法
* 一、訪問權限控制符:說明該方法的容許被訪問的範圍,當前咱們所知的是public
* 二、返回值類型:當方法執行完畢後返回的結果,若是不須要返回結果則填寫void說明
* 三、方法名:方法名是標識符,遵照標識符的>>>>命名規則和規範<<<<
* 四、參數列表:參數列表是被調用時須要傳來的被處理的數值,若是不須要傳來值則能夠爲空,可是小括號不能夠省略
* 五、方法體:方法體中包含方法的成員變量和成員方法,是處理問題的流程
* 六、return語句:return語句是返回通過該方法處理後的與返回值類型相同的值並結束當前方法,若是方法不須要返回值【即返回值類型爲void】則return能夠省略或寫爲return;
* 七、補充:在return語句的下方不能夠再編寫任何無效代碼,編譯器會報錯。
* */
訪問權限控制符 返回值類型 方法名(參數列表) {
方法體;
return 返回值;
}
根據以上的結構,咱們如今給上面的Person類添加說話的方法,並說出:我是一個好人。
代碼以下
//定義一我的類
class Person{
//定義人的屬性
String color;
int weight;
float height;
//定義speak方法,因爲無返回值,因此使用void填充到返回值類型那裏
public void speak() {
System.out.println("我是一個好人");
}
}
上面就建立了一個方法speak,方法只有被調用才能被執行,如今咱們來在main方法中調用執行speak方法
public static void main(String[] args){
//建立人的對象
Person per=new Person();
//調用說話的方法
per.speak();
}
上面就完成了方法的調用
然不只僅能夠說話,還能夠計算,咱們再定一個用於計算的方法
/*
* 做者:Alvin
* 功能:爲人類添加說話和計算方法
* 時間:2019年2月24日11:11:21
* */
public class Test2 {
public static void main(String[] args) {
// 建立人的對象
Person per = new Person();
// 調用說話的方法
per.speak();
//調用人的計算方法
per.cal(10, 90);
}
}
// 定義一我的類
class Person {
// 定義人的屬性
String color;
int weight;
float height;
// 定義speak方法,因爲無返回值,因此使用void填充到返回值類型那裏
public void speak() {
System.out.println("我是一個好人");
}
// 定一個cal方法,使用無返回值得類型,增長這我的的計算功能,須要兩個參數
public void cal(int a, int b) {
System.out.println(a + b);
}
}
咱們看到,這個計算方法cal和說話方法speak不一樣,cal須要傳遞一個參數,而speak方法不須要傳遞參數,共同特色是即便不傳遞參數小括號也不會省略。有傳遞參數時傳遞的參數必須和方法中參數列表中的參數類型和順序相同。
咱們如今修改一下計算方法,讓計算的結果不讓它人類定義的那個對象本身說出來,讓他計算出來後把答案傳給另外一個變量,而後系統經過變量把結果說出來。
/*
* 做者:Alvin
* 功能:爲人類修改有返回值的計算方法
* 時間:2019年2月24日11:19:23
* */
public class Test2 {
public static void main(String[] args) {
// 建立人的對象
Person per = new Person();
// 調用說話的方法
//per.speak();
//調用人的計算方法並把結果傳給result變量,接收結果的變量應和方法返回值類型一致
int result=per.calc(10, 90);
//把獲得的結果經過系統輸出
System.out.println(result);
}
}
// 定義一我的類
class Person {
// 定義人的屬性
String color;
int weight;
float height;
// 定義speak方法,因爲無返回值,因此使用void填充到返回值類型那裏
// public void speak() {
// System.out.println("我是一個好人");
// }
// 定一個cal方法,使用有返回值類型,增長這我的的計算功能,須要兩個參數
public int calc(int a, int b) {
return a + b;
}
}
上面的方法就是有返回值得方法,經過return返回結果。
二、方法的重載
經過上面沒有返回值的計算方法cal咱們知道,咱們知道咱們如今的人類已經能夠計算兩個數的加法了,可是咱們發現若是咱們輸入兩個不一樣時爲int類型的數值傳給該方法則編譯器會報錯,因此,咱們如何能同時讓它能夠計算其餘類型的數值只和呢?如計算2.1+3等類型的和。
咱們只須要經過對上面的方法進行修改就能夠完成了,代碼以下
/*
* 做者:Alvin
* 功能:方法的重載
* 時間:2019年2月24日11:38:18
* */
public class Test2 {
public static void main(String[] args) {
// 建立人的對象
Person per = new Person();
//調用人的計算方法
per.cal(10, 90);
//計算兩個浮點數之和
per.cal(2.1, 3.9);
// 定一個cal方法,能夠計算一個整數,一個浮點數之和
per.cal(2, 3.9);
// 定一個cal方法,能夠計算一個浮點數,一個整數之和
per.cal(2.1, 3);
}
}
// 定義一我的類
class Person {
// 定義人的屬性
String color;
int weight;
float height;
// 說明一、定一個cal方法,能夠計算兩個整數之和
public void cal(int a, int b) {
System.out.println(a + b);
}
//說明二、 定一個cal方法,能夠計算兩個浮點數之和,使用double是由於java中浮點數的默認數據類型爲double
public void cal(double a, double b) {
System.out.println(a + b);
}
//說明三、 定一個cal方法,能夠計算一個整數,一個浮點數之和
public void cal(int a, double b) {
System.out.println(a + b);
}
//說明四、 定一個cal方法,能夠計算一個浮點數,一個整數之和
public void cal(double a, int b) {
System.out.println(a + b);
}
}
計算結果
經過以上編碼,咱們實現了要求的功能。經過觀察總結以下
一、類中的實現計算的各個方法名徹底相同
二、不一樣方法的參數列表所有不一樣
像以上定義方法的形式咱們稱爲方法的重載。
方法的重載(overload)的定義:在同一個類中,方法名同樣,參數列表不一樣,這些方法稱爲方法的重載,做用是在同一類中同一方法的不一樣實現方式,具體調用那個方法取決於接收的參數。
參數列表不同,有三種狀況:
a.參數個數不同
如:
//此方法用來返回兩個數的最小值,若是輸入一個數就返回該參數自己
public int test(int a) {
return a;
}
public int test(int a, int b) {
if (a > b)
return b;
else
return a;
}
b.參數類型不同
講解見說明1和說明2
c.參數順序不同
見說明3和說明4
高能提示:方法的重載中只與上面兩個方面有關,若是返回值類型不一樣不能構成重載。
三、爲何要進行方法重載
方法重載對編程人員來講是十份方便的,咱們知道java是強類型語言,對數據類型有嚴格的限制,若是不予許重載,那麼若是要解決上方計算兩個數的和就須要程序員定義四個不一樣名字的方法,而經過重載只須要定義一個方法cal就能夠了,大大減小了程序員編碼的負擔。使用相同的方法名,當進行調用時JVM會自動選擇調用哪個方法,不須要人爲干預。
四、方法調用的流程
如圖,瞭解就行,看下面兩幅圖
圖一

圖二

五、方法的聲明
必須明確一點方法的聲明和方法的定義最大的區別是沒有函數體!!!對,你沒有看錯,沒有函數體,樣式以下
如
public int test(int a);/*方法的聲明,注意大括號也沒有*/
你們或許顧慮,一個方法沒有方法體爲何要聲明呢?方法的聲明是爲了在之後的抽象類和接口中使用的。目前先不涉及該內容,後續會有講解。再次提醒注意沒有大括號!!小括號後面直接跟英文分號;
到此爲止,咱們目前更加完善了對類的認識。

第五講 構造方法
1、類的構造方法介紹
什麼是構造方法?下面咱們提出一個需求:咱們在用前面建立的人類建立一個對象後,須要再給已經建立的對象的屬性賦值。若是如今要求,在建立人類的對象以後就直接指定該對象的年齡和姓名,該怎麼作?此時使用構造方法就能夠解決。這句話就能夠表達構造方法的做用。你能夠在定義類的時候定義一個構造方法。
構造方法是類的一種特殊方法,它的主要做用是完成對使用該類建立的對象的初始化。它具備如下特色
①、方法名和類名相同
②、沒有返回值類型,就是在方法的返回值類型處空下來
③、在建立一個類的新對象時,系統會自動的調用該類的構造方法完成對新對象的初始化
案例以下
class Test2 {
public static void main(String[] args) {
Person per=new Person(30,"xiaoming");
System.out.println("名字:"+per.name+"體重:"+per.weight);
}
}
//定義一我的類
class Person{
//定義人的屬性
String name;
int weight;
float height;
public Person(int weight,String name) {
this.weight=weight;
this.name=name;
}
//定義speak方法,因爲無返回值,因此使用void填充到返回值類型那裏
public void speak() {
System.out.println("我是一個好人");
}
}
執行結果
從這裏咱們瞭解到其實在new運算符後面跟着的就是構造方法。當咱們建立人類對象並經過new實例化這個對象,經過構造方法傳過去的參數能夠完成對對象屬性在初始化時的屬性賦值。
從如今開始咱們開始使用開發工具進行編碼。咱們使用的開發工工具是eclipse【你們目前你們都在轉用idea,我感受開發工具適合本身最好。】,開發工具能夠在eclipse的官網下載,無償使用【idea是收費的】。
至於開發工具的使用方法你們能夠自行百度,搜索步驟,第一步搜索下載你想使用的開發工具,第二步搜索如何安裝該開發工具【若是是付費的還須要搜索如何破解,有錢的能夠忽略,直接買,】,第三部搜索如何在該開發工具中建立項目,並建立運行項目。
下面咱們對下面的圖片仔細觀察第7行代碼

上面這種狀況是爲何呢?爲何當咱們沒有對Person類寫構造方法的時候調用該類的構造方法是對的,當咱們定一個構造方法反而報錯了呢?
緣由是當該Person類沒有建立構造方法時,系統會自動生成一個構造方法,當咱們本身寫了一個構造方法後系統就再也不爲該類建立構造方法,若是想恢復默認的構造方法只須要刪除咱們定義的方法,或者寫一個空的構造方法。
構造方法與其餘方法的不一樣點除了是在new運算中初始化成員變量的屬性,其餘的特色和普通的方法大體相同,構造方法也能夠被重載。
最後小結:
①、方法名和類名相同
②、構造方法沒有返回值
③、主要做用是完成對新對象屬性的初始化,嚴重鄙視在構造方法中執行一些方法操做,如鏈接數據庫等
④、在一個類中能夠有多個構造方法,即構造方法能夠被重載
⑤、每一個類都有一個默認的構造方法
來如今咱們再來看一下咱們目前對類的瞭解範圍

恭喜、又對類有了更深一層的認識。
第六講 this、類變量
1、this代詞
在本節。咱們將瞭解什麼是this代詞。其實在上面的程序中咱們已經見過this這個代詞。
就在這張圖中的第21和22行

那麼這個this到底是幹什麼的呢?其實這個this是用來爲建立的對象而設計的關係代詞,也就是說當咱們用上面的Person類去建立兩個該類的對象時,jvm就爲這兩個對象分別建立了this代詞,因此每一個對象都有一個this。這個代詞就至關於咱們每一個人都說「個人」這個代詞時全部的指向都是本身同樣。建立了哪一個對象,哪一個對象就擁有他所擁有的this。下面咱們看一個例子
/*
* 做者:Alvin
* 功能:講解this代詞
* 時間:2019年2月25日12:59:48
* */
public class Test {
public static void main(String[] args) {
Dog dog1=new Dog("小黃",34);
Dog dog2=new Dog("小白",23);
dog1.showInfo();
dog2.showInfo();
}
}
class Dog{
String name;
int age;
public Dog(String name,int age) {
this.age=age;
this.name=name;
}
public void showInfo(){
System.out.println("這條狗的名字是:"+this.name+";這條狗的年齡是:"+this.age);
}
}
輸出結果爲
這條狗的名字是:小黃;這條狗的年齡是:34
這條狗的名字是:小白;這條狗的年齡是:23
經過以上案例咱們看到,在輸出語句的this.age和this.name雖然相同,可是這是兩個不一樣的狗(對象)作出的信息展現。
而後咱們在定義一我的,讓這我的養一隻狗
/*
* 做者:Alvin
* 功能:講解this代詞
* 時間:2019年2月25日12:59:48
* */
public class Test {
public static void main(String[] args) {
//建立一我的的對象
Person per=new Person("小明", 23);
//訪問方法中基本數據類型成員變量的值
per.showInfo();
//經過對象訪問引用數據類型中的值
per.dog.showInfo();
}
}
//定義人類
class Person{
//定義人屬性
String name;
int age;
//引用數據類型的屬性定義
Dog dog=new Dog("大黃",32);
//定義人類的構造方法
public Person(String name,int age){
//經過this代詞進行賦值
this.name=name;
this.age=age;
}
public void showInfo() {
System.out.println("名字是:"+this.name+"年齡是:"+this.age);
}
}
//定義狗類
class Dog{
String name;
int age;
//狗的構造方法
public Dog(String name,int age) {
//經過this代詞進行賦值
this.age=age;
this.name=name;
}
//用於展現狗的信息
public void showInfo(){
System.out.println("這條狗的名字是:"+this.name+";這條狗的年齡是:"+this.age);
}
}
經過以上代碼咱們發現,在類中訪問引用數據類型的值和訪問通常基本類型成員變量的過程類似,大體相同。
this代詞使用時注意,this不能再該類定義的外部使用,只能在該類定義的方法中使用。
this代詞的應用場景
this關鍵字主要有三個應用:
(1)this調用本類中的屬性,也就是類中的成員變量;
(2)this調用本類中的其餘方法;
(3)this調用本類中的其餘構造方法,調用時要放在構造方法的首行。
應用一:引用成員變量
Public Class Student {
String name; //定義一個成員變量name
private void SetName(String name) { //定義一個參數(局部變量)name
this.name=name; //將局部變量的值傳遞給成員變量
}
}
this這個關鍵字其表明的就是對象中的成員變量或者方法。也就是說,若是在某個變量前面加上一個this關鍵字,其指的就是這個對象的成員變量或者方法,而不是指成員方法的形式參數或者局部變量。
爲此在上面這個代碼中,this.name表明的就是對象中的成員變量,又叫作對象的屬性,然後面的name則是方法的形式參數,代碼this.name=name就是將形式參數的值傳遞給成員變量。
應用二:調用類的構造方法
public class Student { //定義一個類,類的名字爲student。
public Student() { //定義一個方法,名字與類相同故爲構造方法
this(「Hello!」);
}
public Student(String name) { //定義一個帶形式參數的構造方法
}
}
在一個Java類中,其方法能夠分爲成員方法和構造方法兩種。構造方法是一個與類同名的方法,在Java類中必須存在一個構造方法。若是在代碼中沒有顯示的體現構造方法的話,那麼編譯器在編譯的時候會自動添加一個沒有形式參數的構造方法。這個構造方法跟普通的成員方法仍是有不少不一樣的地方。如構造方法一概是沒有返回值的,並且也不用void關鍵字來講明這個構造方法沒有返回值。而普通的方法能夠有返回值、也能夠沒有返回值,程序員能夠根據本身的須要來定義。不過若是普通的方法沒有返回值的話,那麼必定要在方法定義的時候採用void關鍵字來進行說明。其次構造方法的名字有嚴格的要求,即必須與類的名字相同。也就是說,Java編譯器發現有個方法與類的名字相同才把其看成構造方法來對待。而對於普通方法的話,則要求不可以與類的名字相同,並且多個成員方法不可以採用相同的名字。在一個類中能夠存在多個構造方法,這些構造方法都採用相同的名字,只是形式參數不一樣。Java語言就憑形式參數不一樣來判斷調用那個構造方法。
應用三:返回對象的值
this關鍵字除了能夠引用變量或者成員方法以外,還有一個重大的做用就是返回類的引用。
如在代碼中,能夠使用return this,來返回某個類的引用。此時這個this關鍵字就表明類的名稱。如代碼在上面student類中,那麼代碼表明的含義就是return student。可見,這個this關鍵字除了能夠引用變量或者成員方法以外,還能夠做爲類的返回值,這纔是this關鍵字最引人注意的地方。
public Class Student {
String name; //定義一個成員變量name
private void SetName(String name) { //定義一個參數(局部變量)name
this.name=name; //將局部變量的值傳遞給成員變量
}
Return this
}
原文:https://blog.csdn.net/yanwenwennihao/article/details/79375611
2、類變量、類方法
一、類變量
本節咱們將瞭解什麼是類變量和類方法,從名詞上理解就是這些變量和方法是屬於類的。既然是屬於類的,而類就是一個模板,那麼由該類建立的全部對象均可以使用和訪問。
咱們先引入一個例子加深理解
問題:一羣孩子玩堆雪人,不時會有孩子加入,請問當一個孩子加入後如何計算出如今共有多少人在玩?
分析:咱們知道,一個孩子就是一個對象,當一個孩子加入後若是有一個量能夠容許該對象訪問而後讓該變量加一多好?
根據以上分析,在java中就提供了該中類型的變量——類變量!
代碼以下
/*
* 做者:Alvin
* 功能:靜態變量講解以及訪問案例-堆雪人
* 時間:2019年2月25日13:51:01
* */
public class Test {
public static void main(String[] args) {
Child ch1=new Child("小明", 15);
ch1.showInfo();
Child ch2=new Child("小黃", 14);
ch2.showInfo();
Child ch3=new Child("小化", 13);
ch3.showInfo();
Child ch4=new Child("小量", 10);
ch4.showInfo();
}
}
//定義小孩類
class Child{
String name;
int age;
//用static修飾的變量就是靜態變量,該變量就容許全部的Child類建立的對象共享訪問
static int total=0;
//Child的構造方法
public Child(String name,int age) {
//經過this代詞進行賦值
this.age=age;
this.name=name;
}
//用於展現人數的信息
public void showInfo(){
this.total+=1;
System.out.println("如今的人數是:"+this.total);
}
}
輸出結果
如今人數是:1
如今人數是:2
如今人數是:3
如今人數是:4
咱們發現,static修飾的變量不會隨着每次對象的建立而從新賦值爲0,該變量實現了對該類建立的全部對象的共享。
對該靜態變量還有一種訪問方式,就是經過類名進行訪問。上方代碼作以下修改
/*
* 做者:Alvin
* 功能:靜態變量講解及類名訪問案例-堆雪人
* 時間:2019年2月25日13:58:23
* */
public class Test {
public static void main(String[] args) {
Child ch1=new Child("小明", 15);
Child.total+=1;
System.out.println("如今人數是:"+Child.total);
Child ch2=new Child("小黃", 14);
Child.total+=1;
System.out.println("如今人數是:"+Child.total);
Child ch3=new Child("小化", 13);
Child.total+=1;
System.out.println("如今人數是:"+Child.total);
Child ch4=new Child("小量", 10);
Child.total+=1;
System.out.println("如今人數是:"+Child.total);
}
}
//定義小孩類
class Child{
String name;
int age;
//用static修飾的變量就是靜態變量,該變量就容許全部的Child類建立的對象共享訪問
static int total=0;
//Child的構造方法
public Child(String name,int age) {
//經過this代詞進行賦值
this.age=age;
this.name=name;
}
}
輸出結果
如今人數是:1
如今人數是:2
如今人數是:3
如今人數是:4
也就是說靜態變量【或類變量】能夠經過類名直接訪問。
總結:
一、類變量是該類的全部對象共享的變量,任何一個該類的對象去訪問它時取到的都是相同的值,一樣任何一個該類的對象去修改它時修改的也是同一個變量。該變量被分配在內存中的靜態區。
類變量的格式
二、訪問類變量的形式有兩種
類名.類變量名 或者對象名.類變量
一個類的靜態變量能夠被該類的任何一個對象訪問。也是惟一一個能夠經過類名.靜態變量,可是普通變量是不能這麼幹的。
觀察下列程序,說出執行後的結果

執行結果爲:
View Code
對上方代碼的解釋
必須明白的
一、由static修飾的靜態代碼塊[上圖的5-8行]在代碼執行時直接被加載到內存中的靜態區,且僅執行一次自加操做。十份重要的一點,靜態區塊的代碼只會被執行一次,不管建立多少個對象都只是執行一次。
二、構造方法中的自加操做會在對象初始化的時候執行一次自加操做,i變量是靜態變量,因此在對象中的該類構造方法執行的自加操做就會在靜態變量上加1,也就是說每建立一個對象就會執行一次構造方法,執行一次自加操做。
上方的代碼執行的順序是【行號,只寫主要步驟】
4——》7——》16——》9——》11——》17——》19——》11——》20
上方的static代碼塊只執行了一次。即便你不建立Demo3_2對象程序也會直接令動態變量i存儲在內存中。
當你去實例化一個對象的時候,就不會再執行static這塊代碼塊了,其餘代碼就該怎麼走就怎麼走。
拓展文章:>>靜態變量如何在內存中的存放位置<<
二、類方法【又稱靜態方法】
什麼是類方法,爲何有類方法?類方法是屬於全部對象實例的,其形式以下:
訪問修飾符 static 數據返回類型 方法名(){};
注意:類方法中不能訪問非靜態變量(類變量)。
使用:類名.類方法名 或者 對象名.類方法名
下面咱們看一個小案例。(統計學費總和),源碼以下
先定義一個學生類
//定義學生類
class Stu{
int age;
String name;
int fee;
static int totalFee;
public Stu(int age,String name,int fee)
{
this.age=age;
this.name=name;
totalFee+=fee;
}
//返回總學費
public int getTotalFee()
{
return totalFee;
}
而後在main方法中建立對象並實例化
//定義一個Stu對象
Stu stu1=new Stu(29,"當歸",368);
Stu stu2=new Stu(29,"王明",78);
System.out.println("總費用:"+stu2.getTotalFee());
System.out.println("總費用:"+stu1.getTotalFee());
輸出結果以下
雖然咱們看到上面的方法解決了問題,可是咱們發現,對於getTotalFee方法對每個建立的對象都會擁有一個該方法,而且該方法會佔用內存,形成資源的浪費。
因此,若是有一個方法可以被全部Stu建立的類共享就好多了,這樣既節約了資源又達到了目的。這時類方法就可以達到咱們所指望的目的。該方法改造後的結果以下。
//返回總學費
public static int getTotalFee()
{
return totalFee;
}
上面的方法就會被全部共同的類建立的對象所共享。當咱們在調用的時候就不會在內存中再分配內存。
注意一點:java中規則---【本身定的規則】類變量原則上用類方法去訪問
java中規定,類方法中只能夠訪問靜態變量不能夠訪問成員變量。但成員方法中既能夠訪問類變量也能夠訪問成員變量。
總結:
1、類變量和實例變量最大的區別以下
一、加上static稱爲類變量或靜態變量,不然稱爲實例變量
二、類變量是與類相關的,公共的屬性
三、實例變量屬於每一個對象個體的屬性
四、類變量能夠經過類名.類變量名直接訪問【前提是成員訪問權限控制符容許訪問】
2、類方法的小結
一、類方法屬於與類相關的,公共的方法
二、實例方法屬於每一個對象個體的方法
三、類方法能夠經過 類名.類方法名 直接訪問
3、關於靜態代碼塊的最後說明
一、靜態代碼塊只會在建立對象的時候執行一次。
二、不管建立多少個該類的對象,該代碼塊只會執行一次
三、靜態代碼塊的優先級在該類中最高,優於主方法和構造方法
拓展閱讀:>>類靜態成員變量的存儲位置及JVM的內存劃分 <<、>>JVM幾種常量池的區分<<
第七講 類的三大特徵
初步理解類的封裝、繼承、多態。這三大特徵是公認的java面向對象的三大特徵。
在前面咱們定義的類都是抽象出來的共有屬性,造成一個數學物理模型,也稱爲模板,這種研究問題的模板咱們成爲抽象。如只要是人他都有膚色、血型、身高、體重等屬性,那麼咱們把這些人的共有屬性抽象出來造成一個模型就是咱們所須要的人類Person。
一、封裝
所謂封裝就是咱們把抽象出來的屬性和方法都經過類包裹在一塊兒,數據經過類的包裹在內部被保護起來。經過訪問權限控制符只容許其餘部分訪問被受權訪問的操做。舉例來講就像電視機,咱們手裏的遙控器提供的按鈕就是對外受權的操做,至於咱們按下遙控按鈕後的操做咱們沒法碰觸,這些操做都被電視機給包裹起來,咱們惟一能感知的就是顯示器帶來的反饋。
類是經過訪問權限控制符對內部的成員變量和方法進行保護的。
那麼什麼是訪問權限控制符?咱們前面接觸到的訪問權限控制符只有public,其實訪問權限控制符還有三種,以下表
訪問控制修飾符
訪問級別 |
訪問控制修飾符 |
同類 |
同包 |
子類 |
不一樣包 |
公開 |
public |
√ |
√ |
√ |
√ |
受保護 |
protected |
√ |
√ |
√ |
|
默認 |
沒有修飾符 |
√ |
√ |
|
|
私有 |
private |
√ |
|
|
|
說明:java提供四種訪問控制修飾符號控制方法和變量的訪問權限
一、公開級別:用public修飾,對外公開
二、受保護級別:用protected修飾,對子類和同一個包中的類公開
三、默認級別:沒有修飾符號,向同一個包的類公開
四、私有級別:private修飾,只有類自己能夠訪問,不對外公開
首先明確包的概念:

引入:上圖中綠色方框內的是項目(project)名稱,黑色方框內的是java源碼存放的目錄,src是source的縮寫,src目錄下的藍色方框是包(package),橘黃色區域內的是咱們的java源文件存放的位置。
從上面能夠看出包的做用是用來劃分java源文件劃分區域的,例如支付寶開發中,不一樣的模塊會有不一樣的包名進行存儲java源文件,不一樣包名會有不一樣的模塊開發,可是這些模塊中可能涉及到共同的類名,經過包能夠把相同的類名放置在不一樣的包中不至於混淆,而在同一個包中不能夠擁有相同的類名。其實包的本質就是文件夾,經過英文句號.來區分上下級。

介紹:
一、包的做用以下
①、區分相同名字的類
②、當類不少時,能夠很好的管理類
③、控制訪問範圍
二、package關鍵字
每個java源文件都在文件頭部加入了package關鍵字,標明該文件在該項目中所在的包路徑。格式以下
如 :
注意:該行代碼必須放在每一個java文件的第一句,且每一個java文件中只能有一行該代碼。
三、包的命名規範
包在命名的時候字母務必所有小寫。
四、經常使用包
在咱們之後的開發中會常常用到如下包【無需記憶,須要的時候經過ctrl+shift+O快捷鍵就能夠導入】
java.lang.*;
java.util.*;
java.net.*;
java.awt.*;
下面咱們演示一個動態圖,用來展現包的導入。
五、包的導入
導入格式
引入包的目的是要使用該包內的方法。
以下導包案例
好了,下面經過一個例子來感覺一下部分訪問控制修飾符的做用。
咱們來建立一個職員類Clerk,要求不能隨便查看職員的工資年齡和隱私。這個時候在設計類的時候咱們就須要經過訪問控制修飾符進行處理了。源碼以下
//職員
class Clerk
{
//職員的工資和年齡屬性經過private進行修飾
public String name;
private int age;
private float salary;
public Clerk(String name, int age, float salary){
this.name=name;
this.age=age;
this.salary=salary;
}
}
在上方的private修飾的屬性,按照上面的表格的說明,被修飾的屬性只能在該類,即Clerk類中被訪問,在其餘類中不能被訪問。
若是咱們強行在其餘類中進行訪問,則代碼以下

咱們看到,雖然咱們在其餘類中用對象強行訪問,可是編譯器會報錯的。
那麼既然類中的成員變量經過private訪問控制修飾符再也不對該類之外開放訪問權限,對數據進行了保護,咱們如何進行訪問該變量呢?
咱們能夠經過在該類內部定義一個能夠跨類訪問的方法進行訪問(如protected),如
/*
* 做者:Alvin
* 功能:訪問權限控制符-protected
* 時間:2019年2月26日09:24:20
* */
public class AnyExercise {
public static void main(String[] args) {
// TODO Auto-generated method stub
Clerk cler = new Clerk("zhao", 23, 43);
//經過Clerk中的protected修飾的方法進行訪問。
System.out.println(cler.getAge());
System.out.println(cler.getSalary());
}
}
// 職員
class Clerk {
// 職員的工資和年齡屬性經過private進行修飾
public String name;
private int age;
private float salary;
public Clerk(String name, int age, float salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
//訪問被private訪問控制修飾符修飾的成員變量的
protected int getAge() {
return age;
}
//訪問被private訪問控制修飾符修飾的成員變量的
protected float getSalary() {
return salary;
}
}
執行結果以下
能夠看出,咱們經過訪問protected修飾的方法是能夠訪問到類中的靜態變量的,實現了類變量的跨類訪問.既然protected能夠跨類訪問,那麼他能夠跨包訪問麼?咱們來測試一下
首先在包com.jihaiyang包【關於包的定義能夠自行百度】中定義一個public類,而後定義兩個protected修飾的方法。結構以下圖

在類Clerk中輸入以下代碼
//職員
public class Clerk {
// 職員的工資和年齡屬性經過private進行修飾
public String name;
private int age;
private float salary;
public Clerk(String name, int age, float salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
//訪問被private訪問控制修飾符修飾的成員變量的
protected int getAge() {
return age;
}
//訪問被private訪問控制修飾符修飾的成員變量的
protected float getSalary() {
return salary;
}
}
在Test類中輸入以下代碼
/*
* 做者:Alvin
* 功能:訪問權限控制符-protected-驗證跨包訪問
* 時間:2019年2月26日09:31:10
* */
public class Test {
public static void main(String[] args) {
Clerk cler=new Clerk("xiaowang",2,28);
cler.getAge();
}
}
咱們看到,被protected修飾的方法getAge和getSalary是不可以跨包訪問的,即便咱們強制編寫訪問該方法,編譯器是報錯的

上方都驗證了private和protected修飾後的訪問範圍。默認修飾符下是容許同包中訪問的,如都在com.Alvin下進行訪問,而在子類【在繼承中講解】中是不容許被訪問的,protected容許子類進行訪問,public容許同一個工程內全部範圍的訪問,不一樣工程之間是不容許被訪問的,除非哪一個項目打包後被這個項目引用。
在回顧一下剛開始將的訪問控制修飾符


咱們前面也見到過static,那麼當多個訪問控制修飾符同時出現時的書寫規範是什麼?
oracle.com教材中描述,若是兩個或兩個以上的(不一樣的)字段修飾符出如今字段聲明,它們出現的順序需與FieldModifier一致,這只是習慣,但不是必需的。
若是之後咱們想在JSP頁面中經過標籤來操做Java類,那麼咱們所寫的Java類就必須遵照JavaBean規範。JavaBean類的組成包括其屬性和方法。
一個完整規範類————JavaBean的規範
一、JavaBean類必須是一個公共類。
二、JavaBean類中必須包含兩個類,一個是無參的構造方法,一個是全參的構造方法。
三、JavaBean類中全部的成員變量必須爲私有,必須被private修飾。
四、JavaBean類中必須爲私有成員變量提供set和get方法
參考講解>>javaBean規範<<、>>【瞭解】什麼是javabean及其在JSP中的用法<<
二、繼承
繼承是什麼?爲何要繼承,咱們先看一段代碼
//小學生類
class pupil{
//定義成員屬性
private int age;
private String name;
private float fee;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getFee() {
return fee;
}
public void setFee(float fee) {
this.fee = fee;
}
//繳費
public void pay(float fee)
{
this.fee=fee;
}
}
//中學生類
class MiddleStu{
//定義成員屬性
private int age;
private String name;
private float fee;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getFee() {
return fee;
}
public void setFee(float fee) {
this.fee = fee;
}
//繳費
public void pay(float fee)
{
this.fee=0.8f*fee;
}
}
//大學生類
class ColStu{
//定義成員屬性
private int age;
private String name;
private float fee;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getFee() {
return fee;
}
public void setFee(float fee) {
this.fee = fee;
}
//繳費
public void pay(float fee)
{
this.fee=0.5f*fee;
}
咱們看一下,上面的代碼在定義小學生,中學生和大學生類的過程當中,大部分的代碼都是相同的,因此這樣寫代碼看起來太過於冗餘,沒有提升代碼的複用率,影響板式。那麼有沒有什麼方法能夠這樣:先在一個類中把全部類共用的屬性或方法給封裝起來,當其餘的類在定義的時候能夠直接從中繼承這個類,達到即編寫美觀又提升代碼的複用率呢?答案很顯然是有,看以下代碼
修改後的代碼以下
//咱們先定義一個學生類,把是學生都共有的屬性都寫上
class Stu{
//定義成員屬性
public int age;
public String name;
public float fee;
}
//小學生類經過extends關鍵字繼承學生類
class pupil extends Stu{
//繳費
public void pay(float fee)
{
this.fee=fee;
}
}
//中學生類
class MiddleStu extends Stu{
//繳費
public void pay(float fee)
{
this.fee=0.8f*fee;
}
}
//大學生類
class ColStu extends Stu{
//繳費
public void pay(float fee)
{
this.fee=0.5f*fee;
}
}
經過對比咱們發現,除了第一個定義的學生類Stu,其餘類都符合以下的定義格式
訪問控制修飾符 class 類名 extends 父類名{
成員變量和方法;
}
上面的格式就是類的繼承的定義格式。
咱們知道在現實世界中,咱們會從父輩繼承父輩已有的特性,而編程中的繼承思想正好對應了現實生活中的繼承,使編程更符合人類思惟。當多個類具備多種相同屬性時能夠從這些類中抽象出來一個父類,把相同的屬性封裝在父類中,其餘的類【即子類】經過繼承父類來直接擁有父類的一些特徵【不必定是所有特徵,由於父類中若是成員變量被某些變量、方法修飾則該變量、方法是不能夠被繼承的】,這種作法即提升了代碼利用率,有使代碼簡潔易懂。
那麼父類中的那些方法和屬性能夠被子類繼承呢?
此處再也不用代碼演示【由於靜態的東西沒法直觀的展現,錄製動態圖又太大了,沒法上傳。】,直接記住結論
結論:父類的public、protected、默認【就是不寫修飾符】修飾的屬性和方法能夠被子類繼承,而父類中private修飾的屬性和方法不能夠被子類繼承。因此編程中若是你不想子類繼承父類中的某個方法或屬性就能夠用private修飾符進行修飾。
有些人可能會把訪問修飾控制符的訪問權限和類在繼承父類中的繼承範圍給混淆。訪問控制修飾符是用來對修飾後的成員變量和成員方法在不一樣範圍的類和包中的訪問進行限制的,而子類可否繼承自父類的哪些屬性只存在於子類和父類之間的關係,結論代表只要被private修飾的成員屬性和方法都不能夠被繼承,只要不被private修飾的成員方法和成員變量均可以被繼承。
繼承的注意事項:
一、子類只能繼承一個父類【就像兒子只能有一個爸爸】,java中能夠經過接口來變相彌補這個弊端,另外Java中容許A類繼承B類,而後B類又是繼承自C類...這種類型的多層繼承。
二、java中全部類都是Object的子類【不信能夠查JDK 的API文檔】
三、java中有3000多個類,咱們只需掌握150個類就好了
四、編程過程當中不知道怎麼辦了怎麼辦————問Google大神!彷佛如今只能百度了
五、多查jdk幫助文檔
六、開發過程當中有些類必須使用jdk提供的已經寫好的類
當前咱們對類的認識程度以下圖

方法的覆蓋【也稱方法的重寫】前面咱們介紹了方法的重載,瞭解到同一類中能夠經過方法的重載實現同一方法的不一樣實現。下面咱們介紹方法的覆蓋。
首先必須明確的是方法的覆蓋是存在與父類和子類之間的,這不一樣於方法的重載。咱們先看一個以下案例。要求
定義一個父類Animal,定義動物類的成員屬性和成員方法,在定義Animal的子類Cat、Dog,而後讓Dog和Cat擁有發出各自聲音的方法。
源碼以下
/*
* 做者:Alvin
* 功能:方法的覆蓋
* 時間:2019年2月27日11:13:58
* */
public class Test {
public static void main(String[] args) {
//本別建立三個實例並調用三個實例中的方法
Dog dog=new Dog();
dog.say();
Cat cat=new Cat();
cat.say();
Animal ani=new Animal();
ani.say();
}
}
// 定義父類Animal
class Animal {
int age;
String name;
// 定義方法叫,由於是動物類,不一樣的動物會發出不一樣的叫聲,因此此處的叫聲輸出我是動物,我不知道怎麼叫
public void say() {
System.out.println("我是動物,我不知道怎麼叫、、、、、、、");
}
}
// 貓類
class Cat extends Animal{
public void say() {
//貓類,發出喵喵叫
System.out.println("我是貓,喵............");
}
}
//狗類
class Dog extends Animal{
public void say() {
//狗類,發出汪汪叫......
System.out.println("我是狗,汪汪..........");
}
}
運行結果
我是狗,汪汪..........
我是貓,喵............
我是動物,我不知道怎麼叫、、、、、、、
經過以上案例咱們發現,方法的覆蓋會發生在子類中,覆蓋的是父類中的方法, 而且必須是在子類中編寫與父類中如出一轍的同名方法,包括訪問控制修飾符,返回值,方法名和方法參數。覆蓋的目的就是子類想要繼承父類的某個方法可是該方法的做用又和父類的方法有些不一樣就須要方法的覆蓋。例如生活中小明的父親和小明都具備工做的能力,可是小明的父親是醫生,而小明是教師,因此在實現工做方法行爲的時候小明的方法就須要覆蓋父類的醫生工做去作本身的教師工做。
方法覆蓋的注意事項:方法覆蓋總的來說有如下兩點。【總結】
①、子類的方法的返回類型,參數,方法名稱,要和父類方法的返回類型,參數,方法名稱徹底同樣,不然編譯出錯。
②、子類方法不能縮小父類方法的訪問權限。
上發的第二條中,說明訪問控制符是能夠不同的,可是子類中的訪問控制符要麼與父類的訪問控制符要麼權限同樣,要麼比父類的訪問控制符的權限大,但必定不能比父類的訪問控制符權限小。
super代詞的產生爲了解決子類與父類中的成員變量名重名的問題。在繼承中若是父類擁有默認構造,則子類的任何一個構造中都會在該構造的第一句中添加super();語句用來表示引用父類的構造,同理補充this代詞也有構造傳遞。經過super.方法名直接調用父類中的某方法中的方法體能夠調用父類中的該方法。
三、多態
多態這個詞從字面上理解就是一個類【指一個引用或類型】的多種狀態。如咱們前面講到的交學費問題,有小學生類、中學生類和大學生類,而這些類都繼承自Stu類,因此Stu的子類就是Stu類的不一樣狀態,故而繼承能夠實現一個類的多種狀態,故而說繼承是實現多態的一種條件。在後面咱們還要講解一個知識點,接口(interface),接口也能夠經過不一樣的接入而實現不一樣的功能,就像計算機的USB接口能夠插入U盤實現數據存儲、插入攝像頭實現圖形傳輸等,也是經過一個USB實現了不一樣的工做狀態。一樣的例子還用前面講到的Animal類與子類Dog類和Cat類等。另外還有抽象類(abstract)【後續講解】因此說多態實現途徑有繼承、接口和抽象類。那麼咱們如何實現多態呢?
一、多態的繼承實現
下面把之前舉例使用的一個代碼拿過來,該代碼是Animal類和子類Dog、子類Cat的,源碼以下
/*
* 做者:Alvin
* 功能:繼承的演示
* 時間:2019年2月28日14:32:38
* */
public class Test {
public static void main(String[] args) {
//本別建立三個實例並調用三個實例中的方法
Dog dog=new Dog();
dog.say();
Cat cat=new Cat();
cat.say();
Animal ani=new Animal();
ani.say();
}
}
// 定義父類Animal
class Animal {
int age;
String name;
// 定義方法叫,由於是動物類,不一樣的動物會發出不一樣的叫聲,因此此處的叫聲輸出我是動物,我不知道怎麼叫
public void say() {
System.out.println("我是動物,我不知道怎麼叫、、、、、、、");
}
}
// 貓類
class Cat extends Animal{
public void say() {
//貓類,發出喵喵叫
System.out.println("我是貓,喵............");
}
}
//狗類
class Dog extends Animal{
public void say() {
//狗類,發出汪汪叫......
System.out.println("我是狗,汪汪..........");
}
}
運行結果以下
我是狗,汪汪..........
我是貓,喵............
我是動物,我不知道怎麼叫、、、、、、、
如今咱們稍微修改一下上方代代碼-----修改以後代碼以下
/*
* 做者:Alvin
* 功能:多態實現的演示————繼承
* 時間:2019年2月28日14:38:15
* */
public class Test {
public static void main(String[] args) {
//本別建立三個實例並調用三個實例中的方法
/*
Dog dog=new Dog();
dog.say();
Cat cat=new Cat();
cat.say();
Animal ani=new Animal();
ani.say();
*/
//本次修改的代碼以下
//使用父類定義對象,使用子類引用
Animal ani=new Animal();
ani.say();
Animal aniCat=new Cat();
aniCat.say();
Animal aniDog=new Dog();
aniDog.say();
}
}
// 定義父類Animal
class Animal {
int age;
String name;
// 定義方法叫,由於是動物類,不一樣的動物會發出不一樣的叫聲,因此此處的叫聲輸出我是動物,我不知道怎麼叫
public void say() {
System.out.println("我是動物,我不知道怎麼叫、、、、、、、");
}
}
// 貓類
class Cat extends Animal{
public void say() {
//貓類,發出喵喵叫
System.out.println("我是貓,喵............");
}
}
//狗類
class Dog extends Animal{
public void say() {
//狗類,發出汪汪叫......
System.out.println("我是狗,汪汪..........");
}
}
執行結果
我是動物,我不知道怎麼叫、、、、、、、
我是貓,喵............
我是狗,汪汪..........
咱們注意到咱們在定義的是Animal類型的對象,然而咱們在new的時候倒是分別使用了它的子類Cat類和Dog類,最後輸出的結果也分別輸出了各自new出來的對象的方法給出的結果。這說明JVM虛擬機會自動判斷咱們咱們所定義的父類和子類之間的關係,當咱們定義一個父類對象的時候,若是咱們new的是子類,那麼系統就會自動把子類的對象的引用賦值給左側的父類對象,此時左側父類對象的類型仍然是父類的類型,沒有發生轉變。咱們來驗證一下。以下代碼所示

咱們注意到,當咱們試圖把已經保存有Cat類型引用的變量aniCat的值賦值給新建立的Cat類型的變量cat時,編譯器提示如圖所示的錯誤,因此說雖然能夠用父類保存子類的引用但系統沒有對父類的類型進行轉換,父類類型仍然是父類的類型。咱們再用代碼驗證一下這段藍紫色底紋的文字。

上方特性體現了繼承在多態中的應用。
劃重點還有咱們必須遵照的一點,繼承中多態的實現依賴於方法的覆蓋【也稱重寫】。即若是咱們的Animal類中沒有say方法,而子類中有say方法,那麼咱們就不能用上述的方法去調用子類中的say方法。而若是咱們子類中沒有say方法而父類中有say方法,那麼咱們若是這樣調用就直接調用父類中的say方法。
如圖【子類沒有say方法的調用結果】

如圖【父類沒有say方法的調用結果】

最後展現一個經過多態實現的小案例————狗吃骨頭貓吃魚,來體會一下多態。
要求:定義一個主人,當主人調用feed方法時若是傳給的是狗和骨頭就出狗愛吃骨頭,若是傳給的是貓和魚,就輸出貓愛吃魚。
源碼以下
/*
* 做者:Alvin
* 功能:實現主人餵食案例
* 時間:2019年2月28日16:16:15
* */
public class FeedPet {
public static void main(String[] args) {
// TODO Auto-generated method stub
Master master=new Master();
master.feed(new Dog(), new Bone());
master.feed(new Cat(), new Fish());
}
}
//建立主人類
class Master{
public void feed(Animal animal,Food food) {
animal.showInfo();
food.showInfo();
}
}
//建立食物類
class Food{
public void showInfo() {
}
}
//建立魚類
class Fish extends Food{
public void showInfo() {
System.out.println("魚");
}
}
//建立骨頭類
class Bone extends Food{
public void showInfo() {
System.out.println("骨頭");
}
}
//建立動物類
class Animal{
public void showInfo(){
}
}
//建立狗類
class Dog extends Animal{
public void showInfo() {
System.out.println("我是狗,我喜歡吃骨頭");
}
}
//建立貓類
class Cat extends Animal{
public void showInfo() {
System.out.println("我是貓,我喜歡吃魚");
}
}
輸出結果
我是狗,我喜歡吃骨頭
骨頭
我是貓,我喜歡吃魚
魚
拓展:>>Java子類與父類之間的類型轉換<<
1.向上轉換
父類的引用變量指向子類變量時,子類對象向父類對象向上轉換。從子類向父類的轉換不須要什麼限制,只需直接將子類實例賦值給父類變量便可,這也是Java中多態的實現機制。
2.向下轉換
在父類變量調用子類特有的、不是從父類繼承來的方法和變量時,須要父類變量向子類轉換。
爲何要向下轉換?
在繼承關係中,有一些方法是不適合由父類定義並由子類繼承並重寫的,有些方法是子類特有的,不該該經過繼承獲得,且子類可能也會有本身特有的成員變量,那麼在使用多態機制的時候,若咱們要經過父類類型變量使用到這些子類特有的方法或屬性的話,就須要將父類類型變量轉換成對應的子類型變量。一個典型例子即是標準庫中的數據類型包裝類:Integer類,Double類,Long類等,它們都繼承自Number類,且它們都有一個方法叫作compareTo用於比較兩個一樣的類型。然而這個方法是這些子類經過實現Comparable接口來實現的,在Number類中並無該方法的實現,所以若要經過Number類型變量來使用compareTo方法,就要先將Number類轉換成子類的對象。
注意
父類變量向子類轉換必須經過顯式強制類型轉換,採起和向上轉換相同的直接賦值方式是不行的;而且,當把一個父類型變量實例轉換爲子類型變量時,必須確保該父類變量是子類的一個實例,從繼承鏈的角度來理解這些緣由:子類必定是父類的一個實例,然而父類卻不必定是子類的實例。在進行父類向子類的轉換時,一個好的習慣是經過instanceof運算符來判斷父類變量是不是該子類的一個實例,不然在運行時會拋出運行異常ClassCastException,表示類轉換異常。
咱們經過源碼來只管感覺一下,仍是上面案例,我把main方法的內容修改一下
public static void main(String[] args) {
//使用父類定義對象,使用子類引用
Animal ani=new Animal();
ani.say();
Animal aniCat=new Cat();
aniCat.say();
//下方的aniCat是在13行得來
//此時的aniCat父類對象是子類Cat的一個實例
//不能夠轉換
Cat cat=(Cat)aniCat;
cat.say();
//下方的ani是在11行獲得
//此時的ani父類對象不是子類Cat的一個實例
//不能夠轉換
Cat cat1=(Cat)ani;
cat1.say();
}
執行結果以下

多態中能夠實現向上的自動轉換,可是在處理向下的轉化中,可能會出現類型轉換異常,因此在進行轉換前建議經過instanceof運算符判斷如下類型,繼承中沒有爺爺類一說,只有父類。
二、多態的抽象類實現
一、首先咱們要解決的問題是什麼是抽象類?
咱們先看一個案例,在前面咱們定義的Animal類中的say()方法實際上在子類中一直沒有用到父類中的say()方法,子類所使用的say()方法都是通過本身重寫的say方法。也就是說父類中的方法體徹底沒有必要寫,當不一樣的子類去繼承父類的時候,子類老是會重寫該方法去實現本身所要達到的功能。
// 定義父類Animal
class Animal {
int age;
String name;
// 定義方法叫,由於是動物類,不一樣的動物會發出不一樣的叫聲,因此此處的叫聲輸出我是動物,我不知道怎麼叫
public void say() {
System.out.println("我是動物,我不知道怎麼叫、、、、、、、");
}
}
上方所表達的就是父類方法在子類方法中實現的不肯定性。那這麼說的話咱們如何更好地去書寫代碼呢?這就須要抽象方法來解決了。
首先看一下定義:被abstract(單詞意思抽象)修飾的方法稱爲抽象方法,被abstract修飾的類稱爲抽象類。定義就這麼簡單,舉個案例瞭解一下。就像剛纔的Animal類中的say()方法咱們就能夠書寫爲
abstract public void say();
抽象類的編寫舉例以下
abstract public class Animal{
String name;
abstract public void say();
}
二、關於抽象類和抽象方法的規則以下
一、抽象類不能夠被實例化
二、抽象類仍然能夠被繼承
三、抽象類的子類必須實現抽象類中的全部抽象方法【就是把抽象類中的抽象方法給重寫成完整的功能】
四、抽象類中能夠沒有抽象方法【也就是說抽象類中能夠有其餘完整的方法】
五、含有抽象方法的類必定要命名爲抽象類
六、抽象方法必定不能在定義的時候在抽象類中實現,也不能寫大括號【大括號被認爲是函數主體的存在】
三、何時使用它?
前面咱們已經講了抽象類是用來解決父類方法在子類方法中實現的不肯定性的,因此當咱們父類中的一個方法在大部分子類中都會被重寫的話就能夠把該類聲明爲抽象類,類中的該方法聲明爲抽象方法,至於用不用在實際開發過程當中不多用,可是公司面試問的挺多。
下面咱們經過案例來理解一下抽閒類的多態實現【從原有案例修改獲得源碼】
/*
* 做者:Alvin
* 功能:抽象類的多態實現案例
* 時間:2019年2月28日18:02:42
* */
public class AnimalTest {
public static void main(String[] args) { //此處若是直接定義抽象類對象並用Animal實例會報錯 //Animal ani=new Animal(); //定義父類變量爲子類的實例 Animal aniCat=new Cat(); aniCat.say(); //定義父類變量爲子類的實例 Animal aniDog=new Dog(); aniDog.say(); } } // 定義抽象類Animal abstract class Animal { int age; String name; //由於該方法被全部子類 abstract public void say(); } // 貓類 class Cat extends Animal{ //實現抽象類中的say方法 public void say() { //貓類,發出喵喵叫 System.out.println("我是貓,喵............"); } } //狗類 class Dog extends Animal{ //實現抽象類中的say方法 public void say() { //狗類,發出汪汪叫...... System.out.println("我是狗,汪汪.........."); } }
執行結果
我是貓,喵............
我是狗,汪汪..........
三、多態的接口實現
之前只簡單說了接口,那麼什麼是接口?
仍是同樣的舉例,以USB爲例,不一樣的廠商在生產USB插頭的時候他們生產的USB插頭的標準都是同樣的,不然就不能插入適配,可是插入USB的設備實現是不同的,有的是存儲設備,有的是照相設備等,而接口只是提供了一個能夠發生交換的通道。
程序中的接口也是這樣,java中接口就是封裝在一塊兒的沒有內容的方法,當某個類想用的時候在根據具體狀況把它寫出來實現。
實現接口的格式以下
class 類名 implements 接口名1,接口名2,....{
方法;
變量;
}
咱們發如今關鍵字implements後面能夠跟多個接口名,這說明一個類能夠同時實現多個接口。
而後再看一下接口的定義格式
經過以上講解咱們能夠這麼類比

方法名對應排線是由於在接口中有好多方法,可是具體使用哪個根據具體狀況而定,而在USB的排線中會根據不一樣功能而是用不一樣的線進行傳輸。
能夠這麼說,接口時更加抽象的抽象類,由於抽象類中的方法能夠有方法體,而接口中的每個方法都不能被實現接口體現了高內聚低耦合的程序設計思想。
下面咱們定義一個接口
interface Usb{
//定義一個變量
int a=1;
//聲明兩個方法
public void start();
public void stop();
}
上面咱們就定義了一個Usb接口,並聲明瞭兩個方法,用來在USB啓用和中止的時候被調用的方法。接下來咱們接着來實現接口。咱們定義一個相機類用來實現接口
//定義一個相機類
class Camera implements Usb{
//相繼開始使用USB接口
public void start() {
System.out.println("我是相機,開始工做了!");
}
//相繼中止使用USB接口
public void stop() {
System.out.println("我是相機,中止工做了!");
}
}
接着咱們定義計算機類,用計算機去建立方法調用USB
//定義一個計算機類
class Computer{
//計算機的USB被喚醒加載
public void useUsb(Usb usb) {
usb.start();
usb.stop();
}
}
最後咱們在測試類中定義計算機對象並把相機加載到USB接口上
public static void main(String[] args) {
Computer computer=new Computer();
computer.useUsb(new Camera());
}
這樣就完成了對USB接口的多態實現。執行結果以下
如今咱們再添加一個設備,U盤,而後再傳給計算機的USB接口,代碼以下
//定義一個U盤類
class uDisk implements Usb{
//U盤開始使用USB接口
public void start() {
System.out.println("我是U盤,開始工做了!");
}
//U盤相繼中止使用USB接口
public void stop() {
System.out.println("我是U盤,中止工做了!");
}
}
測試類中的main方法添加
Computer computer=new Computer();
computer.useUsb(new Camera());
computer.useUsb(new uDisk());
執行結果
我是相機,開始工做了!
我是相機,中止工做了!
我是U盤,開始工做了!
我是U盤,中止工做了!
接口使用時的注意事項【規則】
一、接口不能被實例化
接口不能被實例化是由於接口通常做爲方法的集合體,沒有方法體。
二、接口中的全部方法必須知足如下要求
接口中的方法要麼爲抽象方法[public abstract] void method();在接口中,public abstract能夠省略。要麼爲默認方法 public default void method(){};再或者爲靜態方法public static void method(){};
三、一個類能夠實現多個接口
一個類能夠實現多個接口而且實現全部接口的全部抽象方法。當遇到實現的多個接口的抽象方法重名問題那麼只須要重寫一個抽象方法就能夠,可是若是出現兩個接口的默認方法重名,那麼必須在實現類重寫一個被重名的方法。Java容許單繼承多實現,當一個類出現接口中的默認方法和父類中的正常方法徹底一致時,因爲父類的優先級比接口高,因此只會調用父類中的方法,接口中的默認方法會被覆蓋。
四、接口中能夠有變量,可是變量不能用private和protected修飾
關於這一點還要聲明
a.接口中的變量本質上就是static的,無論你加不加static修飾,該變量只能用public,static或者final進行修飾
b.在java開發中,咱們常常把常常用的變量,定義在接口中,做爲全局變量進行使用,interface的全部成員變量都被聲明爲最終靜態的,也就是常量。修改是能夠經過繼承的方式重寫的
訪問形式:接口名.變量名
五、一個接口不能繼承其餘的類,可是能夠繼承自其餘的接口
實現接口 與 繼承類 對比
java的繼承是單繼承,也就是一個類最多隻能繼承一個父類,這種單繼承機制保證了類的純潔性,但不能否認對子類的擴展有必定影響,因此咱們認爲:(1)實現接口能夠看作是對繼承的一種補充。還有一點,繼承是層級式的,不太靈活。就像我麼的家譜,若是在任何一個類中它的方法屬性發生更改,那麼該類全部的子類都會發生改變,在某些狀況下這種結果多是災難性的。
而接口就沒有那麼麻煩,加入一個接口發生改變爲了不該接口形成的影響能夠採起在實現該接口的類上移除發生改變的接口就行。
因此有(2)實現接口能夠在不打破繼承關係的狀況下實現功能的擴展。咱們在經過接口實現多態時是經過使用接口類型的變量做爲傳遞的媒介,使凡是繼承了該接口的類都可以經過該接口類型的變量訪問到實現類中的方法,其本質也是經過方法的重寫來實現的。
對多態理解的在深刻
繼承是多態得以實現的基礎,從字面上理解,多態就是一種類型的多種狀態,將一個方法調用同這個方法的主體聯繫起來【即將這個類實例和這個實例的方法聯繫起來】。這種聯繫的調用分爲前期綁定和後期綁定兩種狀況。
①、前期綁定
前期綁定是在程序運行以前進行的綁定,由編譯器和連接程序進行實現,又叫作靜態綁定。好比static方法和final方法,注意這裏也包括private方法,由於他們是隱式final的。
②、後期綁定
在運行時根據對象的類型進行綁定。由方法調用機制進行實現,所以又叫動態綁定和運行時綁定。除了前期綁定只外全部的方法都屬於後期綁定。
多態就是在後期綁定這種機制上實現的。多態給咱們帶來的好處是消除了類之間的耦合關係,使程序更容易拓展,是編寫更加靈活。
接口的最後總結:
一、接口不能被實例化
二、接口中的全部方法不可以有方法體,花括號也不能出現{}
三、一個類能夠實現多個接口
四、接口中的方法能夠有參數列表和返回類型,但不能有任何方法體
四、接口中能夠有變量,可是變量不能用private和protected修飾
關於這一點還要聲明
a.接口中的變量本質上就是static的,無論你加不加static修飾,該變量只能用public,static或者final進行修飾,即接口中的方法能夠被聲明爲 public 或不聲明,但結果都會按照 public 類型處理,接口中能夠包含字段,可是會被隱式的聲明爲 static 和 final,也就是說接口中的字段只是被存儲在該接口的靜態存儲區域內,而不屬於該接口
b.在java開發中,咱們常常把常常用的變量,定義在接口中,做爲全局變量進行使用
訪問形式:接口名.變量名
五、一個接口不能繼承其餘的類,可是能夠繼承自其餘的接口,即擴展一個接口來生成新的接口應使用關鍵字 extends ,實現一個接口使用 implements
六、多態調用方法時的特色:多態繼承關係中,編譯階段驗證父類方法,運行階段運行子類,因此多態只能調用字子父類中共有的方法——即實現基於重寫。
因此說多態實現的前提是重寫!!!!
到如今爲止咱們對類的認識才更加完善

第八講 final修飾符
1、final概念
final中文意思:最後的,最終的
final能夠修飾變量或者方法
在某些狀況下,程序員可能有如下需求:
①當不但願父類的某些方法被子類覆蓋時能夠用final關鍵字修飾【區別於private,private修飾的不能被繼承,而final的能夠被繼承可是不能被重寫】
②當不但願類的某個變量的值被修改,能夠用final修該,能夠用final修飾【區別於static,static修飾的變量是能夠被該類全部的實例共享的,能夠訪問和修改,而final修飾的量是被全部實例容許訪問可是不容許修改】
③、當不但願類被繼承時能夠在類的修飾符中添加final進行修飾
final就是爲了知足以上三個要求的。
對第一條舉例

從上圖能夠看到,愛27行的錯誤提示顯示不能重寫從Phone繼承的final方法
再如第二條

在第14和16行提示了相同錯誤
還有第三條

在第30行對試圖繼承Phone類的HUAWEI類報錯。
2、final的注意事項
①、final修飾的變量又叫常量,通常用 xx_xx_xx來命名
②、final修飾的變量在定義時必須賦值,不然之後就不能賦值了
3、final何時用
①、由於安全考慮,某個類的方法不容許被修改
②、一個類不容許被繼承
③、某些變量值是固定不變的,如π=3.1415926
FBI WARNING
至此,Java的面向對象編程的基本知識已經結束。
第九講 數組
1、一維數組
1、簡單類型的數組
一、案例————求學生年齡的平均數
當咱們去求n個學生的平均年齡時,由於每一個學生的年齡都是整數,因此可讓這n個學生站成一排,由於咱們統計的是學生的年齡平均數,因此和學生的其餘特徵無關,因而咱們對全部學生的年齡起名爲年齡,從第一個學生開始咱們依次給他們起名爲年齡一、年齡二、年齡3....年齡n記錄,而後在把年齡值依次相加最後除以上面的處理思想就相似於java中使用數組進行解決。那麼什麼是數組?數組是能夠存放相同數據類型的數據結構。咱們注意到前面咱們須要統計的都是年齡值,這些年齡值就是咱們須要存儲的元素。因爲不一樣的人有不一樣的年齡值因此有不一樣的名稱,可是他們都是年齡,因此經過總稱「年齡」來表明他們全部人的年齡,經過「年齡+序號」來表示第幾個學生的年齡。在數組中也是用這種方法區分整體和個體的。
二、數組的定義格式
數組的定義格式以下
數據類型 數組名[]=new 數據類型[數組長度];
或
數據類型[] 數組名=new 數據類型[數組長度];
或
數據類型[] 數組名;
數組名=new 數據類型[數組長度];
或
數據類型[] 數組名={元素1,元素2,元素3,元素......};此種定義方法用於數組元素在程序編寫的時候能夠已知的賦值。
如咱們要統計90個學生的平均年齡【年齡是整數】就須要下方這樣定義
上方的int就是「數據類型」,age就是「數組名」,90就是「數組長度」。
三、數組中元素的訪問
咱們經過上方定義了一個長度爲90的數組,那麼數組中的元素是如何訪問呢?java規定,數組中元素的訪問遵照下方的格式。
數組名[下標];
這裏的下標不一樣於咱們生活中的從1開始,這裏的下標是從0開始的到數組長度減1結束。好比咱們要取出第5我的的年齡只須要經過age[4]就能夠取到。那麼爲何數組的下標要從零開始呢?【如下純屬我的理解】系統把數組是分配在一段連續的內存中的。它的結構能夠用下面的圖片簡單示意一下

咱們知道咱們人是在地球上的,咱們在地球上都有一個惟一的家庭地址,相同的事實是咱們運行的程序數據都存儲在內存中,而且這些數據也有地址,咱們家庭的地址使用文字進行描述,而內存中的數據地址經過十六進制的數字依次從內存的一端從小到大向後排列,因此當咱們像內存中申請一個90個長度的int型變量時系統就會在內存中劃出這樣一片連續的區域並把開頭的那個元素的地址返回給數組名。因爲咱們的數據都是有大小的,因此在每一個元素之間都會有該類型大小的空間供存放數據。當咱們在使用數組的某個元素的時候咱們不可能經過地址進行訪問,因此經過數組名加下標的形式進行訪問,而下標就是地址的另外一種呈現形式,它表示從第一個元素頭部開始你訪問的數據向後移動多少個該數組類型所佔空間大小。如當咱們訪問第一我的的年齡時由於age指向的就是第一我的的年齡頭部,因此向後偏移的量【即偏移量(offset)】爲0,因此使用age[0]訪問到了第一我的的年齡數據。
補充:在數組中最經常使用的一屬性是.length屬性,能夠獲取數組的長度
如前面咱們定義的一個長度爲90的數組,咱們能夠經過數組名.length得到該數組的長度。操做以下
int arrayLength=age.length;
經過上述語句就把age數組的長度獲得並賦值給一個新變量arrayLength進行保存。
四、使用數組時的注意事項。
①、數組是定長的。所謂定長就是數組一旦定義長度就不能夠改變,如咱們上面聲明瞭一個長度爲90的數組,當咱們想在該數組中添加元素時是不被容許的。數組大小不能從控制檯輸入。
②、數組是用來保存同一種類型的元素的。在上方定義的數組類型是int型,從圖中咱們看到,在每一個空缺的空間大小都是int個字節,此時若是咱們把一個long類型的數字存進去是不可能的。由於long類型是8個字節。
③、數組的訪問不能夠超過數組的長度,不然會報錯。就像咱們剛纔聲明的一個長度爲90的數組,若是咱們在訪問的時候使用age[90]就會報空指針異常,由於數組的下標是按照偏移量來的,若是使用age[90]那麼就是在訪問第91個元素,可是該元素不存在,因此會報錯。
④、數組在定義的時候必須指定長度。由於數組是定長的,因此定義的時候必須提供長度。
⑤、數組名是指向首個元素的首地址【頭部】
五、數組的遍歷
數組的訪問是經過角標進行的訪問,對數組的遍歷就能夠經過吧角標換成變量而後在循環中進行
代碼以下
2、對象數組
前面以int類型表明基本數據類型介紹了數組,那麼可不能夠有對象數組呢?
一、案例

編寫一個程序,要求求出平均體重,並找出體重最大的和最小的輸出他們的名字。
很顯然,若是使用原來的數組沒法保存上面的全部信息。這個時候咱們就須要定義引用類型來解決。
源碼以下
經過以上方法咱們解決了以上問題。上面解決問題的方式就是使用對象數組。
二、對象數組的定義格式
對象數組的定義格式和基本數據類型的同樣。
三、對象數組的元素訪問
對象數組的元素訪問和基本數據類型的元素訪問也同樣,只不過基本數據類型的數組訪問到的是存儲在內存中的數據,而對象數組訪問到的是數組中的對象,若是想訪問到該對象的屬性還須要經過成員運算符「.」進行訪問。
四、使用對象數組時的注意事項
一、全部基本數據類型的要求
二、在定義對象數組時必須爲數組中的每一個對象進行new操做不然內存中沒有爲該對象分配空間,數組中的對象沒法使用,若是編譯會報空指針異常。
五、對象數組的遍歷
對象數組的遍歷和基本類型數組的遍歷同樣。
第十講 排序
所謂排序就是將一羣數據,依指定的順序進行排列的過程。 也是程序員的基本功。
排序的分類,從大的方面有
①、內部排序
指將須要處理的全部數據加載到內存存儲器中進行排序。包括(交換式排序、選擇是排序、插入式排序)
②、外部排序
當數據量巨大的時候沒法所有加載到內存中,須要藉助外部存儲進行排序,包括(合併排序,和直接合並排序)
排序(Sorting)是一種數據處理中很重要的運算,同時也是很經常使用的運算,通常數據處理工做的25%得時間都在進行排序。
所謂排序就是將一組記錄按照某個域的值進行按照要求進行序列化(如從大到小或者從小到大)操做。
此處講解傳參和傳指的區別。
1、內部排序
一、交換式排序法
交換式排序法屬於內部排序法,是運用數據值比較後依照判斷規則對數據進行位置交換,以達到排序的目的。
交換式排序法分爲兩種(假設排序要求是將整數數字從小到大排列):
①、冒泡排序法(Bubble Sort)
冒泡排序的思想
從第一個元素開始,依次將相鄰的兩個元素進行比較,若是知足前一個數大於後一個數的條件則將這兩個數交換位置。交換後繼續進行比較循環進行。因爲每次排序都會把最大的那個數經過比較移到最後端,因此每完成一次從頭至尾的比較就將比較的範圍縮減1.

源碼以下
class BubbleSort{
public static void main(String[] args) {
// TODO Auto-generated method stub
int arr[]= {1,6,0,-1,9,-10,9,-90,39,20,95,48,39,-39,30};
int temp=0;
//排序,最終按照從小到大的順序進行排序
//外層循環,他決定走幾趟
//通過以上幾個變量的比較,每一輪排序都會把本輪的最大值給排列到最後。多以在內部遍歷的時候遍歷的長度爲arr.length-1-i;
for(int i=0;i<arr.length;i++)
{
for(int j=0;j<arr.length-1-i;j++)
{
if(arr[j]>arr[j+1])
{
//不增長變量的狀況下交換兩個變量的值。
// arr[j]+=arr[j+1];
// arr[j+1]=arr[j]-arr[j+1];
// arr[j]=arr[j]-arr[j+1];
// 添加一個temp變量交換兩個變量的值。
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
for(int j=0;j<arr.length;j++)
{
System.out.println(arr[j]);
}
}
}
②、快速排序法(Quick sort)
該方法是對冒泡排序的一種改進。
思路:經過一趟將要排序的數據分割成兩部分,其中一部分的全部數據比另外一部分的全部數據都要小,而後將產生的兩部分在各自分紅上面要求的形式,以此類推,最後獲得的數據就是排序完成的數據。
快速排序思想:快速排序使用的是分治思想。
特色:採用分治思想,對數據進行劃分同時進行排序。
快速排序的優缺點:
優勢:效率最高時間最快。
缺點:因爲採用的遞歸思想,因此須要等最後一個數出棧其餘的數纔可以接着出棧,故十份消耗內存
源碼以下
class QuickSort{
public void sort(int left,int right,int[] array)
{
int l=left;
int r=right;
int pivot = array[(left+right)/2];
int temp=0;
while(l<r)
{
while(array[l]<pivot) l++;
while(array[r]>pivot) r--;
if(l>=r) break;
temp=array[l];
array[l]=array[r];
array[r]=temp;
if(array[l]==pivot) --r;
if(array[r]==pivot) ++l;
}
if(l==r)
{
l++;
r--;
}
if(left<r) sort(left,r,array);
if(right>l) sort(l,right,array);
}
}
二、選擇式排序法
選擇式排序也屬於內部排序法,是從欲排序的數據中按指定的規則選出某一元素,通過和其餘元素重整,再依要求交換位置後達到排序目的。
選擇式排序又可分爲兩種
①、選擇排序法(Selection Sort)【比冒泡排序法快一點】
選擇排序的思想是從每趟中選出一個較小值,而後記住這個最小值的下標,本趟完成全部對比後會選擇出本次選出的最小值,而後跟每趟開頭的那個元素交換位置。最開始的時候將第一個元素開成最小的元素。

源碼以下
class SelectSort {
public static void main(String[] args) {// 要求將給定的數序列按照從小到大的順序進行排列
// TODO Auto-generated method stub
int arr[] = { 1, 6, 0, -1, 9, -10, 9, -90, 39, 20, 95, 48, 39, -39, 30 };
int temp;// 定義臨時變量用於當條件知足時用於交換數據
int minIndex;// 用於記錄本趟中的最小數值的下標
for (int i = 0; i < arr.length - 1; i++) {// 排序時決定走幾趟
minIndex = i;
for (int j = i + 1; j < arr.length; j++)// 在該趟中選擇出最小的數值給該趟中的第一個交換位置。
{
if (arr[minIndex] > arr[j]) {
minIndex = j;
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
// 輸出排序後的數組信息。
for (int k = 0; k < arr.length; k++) {
System.out.println(arr[k]);
}
}
}
②、堆排序法(Heap Sort)
將排序碼k1,k2,k3.......kn表示成一棵徹底二叉樹,而後從第n/2個排序碼開始篩選,使由該結點做爲根結點組成的子二叉樹符合堆的定義,而後從第n/2-1個排序碼重複剛纔操做,直到第一個排序碼中止。這時候該二叉樹符合堆的定義,初始堆已經創建。
接着,能夠按照以下方法進行堆排序:將堆中的第一個結點(二叉樹根結點)和最後一個結點的數據進行交換(k1與kn),再將k1-kn-1從新建堆,而後k1和kn-1交換,再將k1-kn-2從新建堆,而後k1和kn-2交換,如此重複下去,每次從新建堆的元素個數不斷減1,直到從新建堆的元素個數僅剩一個爲止。這時堆排序已經完成,則排序碼k1,k2,k3,k.....kn已排成一個有序序列。
若排序是從小到大排序,則能夠創建大根堆實現堆排序,若排序是從大到小,則能夠用小根堆實現排序。
堆排序圖示

三、插入式排序法
插入式排序發也屬於內部排序法,是對於欲排序的元素以插入的方式找尋該元素的適當位置,來達到所給元素的序列化。
插入排序法又可分爲三種
①、插入排序法(Insertion sort)【優於選擇排序法】
思想;首選把欲排序的數據分紅有序集和無序集,而後每次從無序集中取出一個元素和有序集中的元素依次比價,若是在有序集中從某個位置開始的值比待插入值小,有序集的後一個元素比待插入值大,則把該元素插入到有序集的這兩個值之間的位置。
代碼以下:
class InsertSort{
public static void main(String[] args) {
// TODO Auto-generated method stub
//插入排序,本例題要求把全部的數據按照從小到大進行排列
int arr[]= {1,6,0,-1,9,-10,9,-90,39,20,95,48,39,-39,30};
for(int i=0;i<arr.length-1;i++)
{
//把將要參與比較的數備份下來。由於後面須要移位
int insertValue=arr[i];
//insertVal準備和前一個數比較
int index=i-1;
while(index>-1 && insertValue<arr[index] )
{
//將把arr[index]向後移動
arr[index+1]=arr[index];
index--;
}
//把數插入到指定位置。
arr[index+1]=insertValue;
}
for(int k=0;k<arr.length;k++)
{
System.out.println(arr[k]);
}
}
}
②、謝爾排序(shell sort)
謝爾排序(又稱希爾排序shell sort)又稱爲「最小增量排序」。該方法的基本思想是:先將整個待排元素序列分割成若干個子序列(由相隔某個「增量」的元素組成的)分別進行直接插入排序,待整個序列中的元素基本有序(增量足夠小)時,再對全體元素進行一次直接插入排序。
由於直接插入排序在元素基本有序的狀況下(接近最好狀況),效率很高,所以希爾排序在時間效率上比前兩種方法有較大提升。速度上快速排序是最快的。
③、二叉樹排序法(Binary-tree Sort)
二分插入(Binary Insert Sort)的基本思想是:在有序表中採用二分查找的方法查找待排元素的插入位置。
其處理過程:先將第一個元素做爲有序序列,進行n-1次插入,用二分查找的方法查找待排元素的插入位置,將待排元素插入。
2、外部排序
合併排序
合併排序是外部排序最經常使用的排序方法。若數據量太大沒法一次完成加載內存,可以使用外部輔助內存來處理排序數據,主要應用在文件排序。
排序方法:
將欲排序的數據分別存在數個文件大小可加載內存的文件中,再針對各個文件分別使用「內部排序法」將文件中的數據排序號寫回文件。再對全部已排序好的文件兩兩合併,直到全部文件合併成一個文件後則數據排序完成。
假設有四個文件A、B、C、D,其內部數據均已排序完成,則文件合併排序方式以下。
(1)將已排序好的A、B合併成E,C、D合併成F,E、F的內部數據分別均已排好序。
(2)將已排好的E、F合併成G,G的內部數據已排好序
(3)四個文件A、B、C、D數據排序完成
第十一講 查詢、多維數組
1、查詢
在java中,咱們經常使用的查找有兩種
①順序查找
速度最慢的一種查找,效率也低。【舉例實現】
②二分查找
只適應於有序數列。要求是先排序再查找,即便執行兩個操做,在大的數據中執行查找操做,二分查找也要比順序查找快。【舉例實現】
2、多維數組
多維數組中最重要的是二維數組,其餘的維度的數組基本上用不到,三維數組在3DmMarks中能夠用到。
①、二維數組的定義
語法:類型 數組名[][] = new 類型[大小][大小];
好比:int a[][]=new int[2][3]
②、分析
二維數組在內存中的存在形式。二維數組在內存中仍然是以一維數組的形式存在的。如上面定義的數組的存儲順序是
a[0][0]a[0][1]a[0][2]a[1][0]a[1][1]a[1][2].....
③、案例,請用二維數組輸出以下圖形
0 0 0 0 0 0
0 0 1 0 0 0
0 2 0 3 0 0
0 0 0 0 0 0
④、案例,要求對
0 0 0 0 0 0
0 0 1 0 0 0
0 2 0 3 0 0
0 0 0 0 0 0
進行轉置。
第十二講 二進制、位運算、移位運算符
1、二進制
一、掌握計算機二進制(源碼、反碼和補碼)
二、充分理解java爲運算和移位運算符
一、基本概念
①、什麼是二進制?
二進制是縫2進位的進位制,0,1是基本算符。
計算機爲何採用二進制?由於在計算機中只有經過高低電平兩個電位計算機在信號表示上才最穩定。也就是說在計算機中的電位要麼是高電位要麼是低電位,而高電位計做1,低電位計做0.
現代的電子計算機技術所有采用的是二進制,由於它只使用0、1兩個數字符號,很是方便,易於用電子方式實現。計算機內部處理的信息,都是採用二進制數來表示的。二進制數用0,1兩個數字及其組合來表示任何數。進位規則是逢2進1,數字1在不一樣的位上表明不一樣的值,按從右至左的次序,這個值以二倍遞增。
②、什麼是原碼、反碼、補碼?
這三個概念是對有符號【就是有能夠表示正數也能夠表示負數】的數字而言的。
二進制的最高位是符號位,0表示正數,1表示負數。
③、原碼、反碼、補碼的轉換規則
①、正數的原碼、反碼、補碼都同樣,是該正數直接轉換成二進制。
②、負數的反碼=它的原碼符號位不變,其餘位取反
③、負數的補碼=負數的反碼+1
④、0的反碼,補碼都是0
⑤、java沒有無符號數,換言之,java中的數都是有符號的
⑥、在計算機運算的時候,都是以補碼的方式來運算的
⑦、整數類型的存儲範圍計算公式
小技巧:對一個數兩次求補碼獲得的結果仍然是這個數自己。
2、位運算符和移位運算符
一、位運算符基本知識
java中有4個位運算符,分別是「按位與&、按位或|、按位異或^,按位取反~」,它們的運算符規則是:
按位與&:兩位全爲1,結果爲1
按位或|: 兩位一個爲1結果爲1
按位異或^:兩位不一樣爲1相同爲0
按位取反~:0->1,1->0
案例介紹
好比:~2=-3 2&3=2 2|3=3 2^3=1
小技巧:(A^B)^A=B
二、移位運算符基本知識
java中有四個位移運算符:
>>、<<算術右移和算術左移,
運算規則:
算術右移:低位溢出,符號位不變,並用符號位補溢出的高位
算術左移:符號位不變,低位補0
>>>邏輯右移,運算規則是:低位溢出。高位補0
小技巧:當正數和負數進行算術左移n位的時候至關於在原來的數值乘以2的n次方,當正數進行算術右移的時候,每移一位至關於除以一次2
第十三講 集合框架
集合框架
目標:掌握經常使用的集合類
什麼是集合類?【我的理解:集合類就至關於一個容器,咱們建立了一個集合對象後就至關於建立了一個容器實例,而後咱們把要處理的實例當成一個處理單元裝入集合類,經過集合類進行管理,而後咱們經過操做集合類的實例中的方法對咱們的數據進行操做。總的來講,集合類是將多個元素組成一個單元的對象。集合類的做用是用於儲存、檢索和操縱數據,以及將數據從一個方法傳輸至另外一個方法。集合操做的目標是對象。集合類的使用都大同小異。】
1、案例
請作一個公司職員薪水管理系統,要求完成以下功能
一、當有新員工時,將該員工加入到管理系統
二、能夠根據員工工號,顯示該員工的信息
三、能夠顯示全部員工信息
四、能夠修改員工的薪水
五、當員工離職時,將該員工從系統管理中刪除
六、能夠按照薪水從低到高順序排序【思考題】
七、能夠統計員工的平均工資和最低、最高工資
根據以上的要求,很顯然不能經過定義數組的方式進行解決,由於數組不能完成上面的增長和刪除員工。
以咱們之前的知識,第一個能想到的解決的問題就是定義鏈表進行解決。由於鏈表能夠動態的改變由鏈表構成的數組的長度。可是鏈表的實現過程過於繁瑣,爲了解決此類方法,java的設計者們爲咱們提供了一系列的集合類。可是當咱們遇到某些很是奇怪的問題的時候就須要本身手寫鏈表進行解決,通常狀況下采用鏈表進行解決就能夠了。
2、使用
咱們先來看一下集合框架圖
圖一

圖例介紹:上述類圖中,實線邊框的是實現類,好比ArrayList,LinkedList,HashMap等,折線邊框的是抽象類,好比AbstractCollection,AbstractList,AbstractMap等,而點線邊框的是接口,好比Collection,Iterator,List等。
圖二

圖二是圖一的簡化圖,從圖中能夠看出上述全部的集合類,都實現了Iterator(迭代器)接口,這是一個用於遍歷集合中元素的接口,主要包含hashNext(),next(),remove()三種方法。它的一個子接口LinkedIterator在它的基礎上又添加了三種方法,分別是add(),previous(),hasPrevious()。也就是說若是是先Iterator接口,那麼在遍歷集合中元素的時候,只能日後遍歷,被遍歷後的元素不會在遍歷到,一般無序集合實現的都是這個接口,好比HashSet,HashMap;而那些元素有序的集合,實現的通常都是LinkedIterator接口,實現這個接口的集合能夠雙向遍歷,既能夠經過next()訪問下一個元素,又能夠經過previous()訪問前一個元素,好比ArrayList。
拓展:迭代器(Iterator)
迭代器是一種設計模式,它是一個對象,它能夠遍歷並選擇序列中的對象,而開發人員不須要了解該序列的底層結構。迭代器一般被稱爲「輕量級」對象,由於建立它的代價小。
Java中的Iterator功能比較簡單,而且只能單向移動:
(1) 使用方法iterator()要求容器返回一個Iterator。第一次調用Iterator的next()方法時,它返回序列的第一個元素。注意:iterator()方法是java.lang.Iterable接口,被Collection繼承。
(2) 使用next()得到序列中的下一個元素。
(3) 使用hasNext()檢查序列中是否還有元素。
(4) 使用remove()將迭代器新返回的元素刪除。
Iterator是Java迭代器最簡單的實現,爲List設計的ListIterator具備更多的功能,它能夠從兩個方向遍歷List,也能夠從List中插入和刪除元素。
還有一個特色就是抽象類的使用。若是要本身實現一個集合類,去實現那些抽象的接口會很是麻煩,工做量很大。這個時候就能夠使用抽象類,這些抽象類中給咱們提供了許多現成的實現,咱們只須要根據本身的需求重寫一些方法或者添加一些方法就能夠實現本身須要的集合類,工做流昂大大下降。
從上面的圖一能夠看出java集合類主要有如下幾種
一、List結構的集合類
ArrayList類,LinkedList類,Vector類,Stack類
二、Map結構的集合類
HashMap類,Hashtable類
三、Set結構的集合類
HashSet類、TreeSet類
四、Queue結構的集合
Queue接口
集合類的功能是能實現動態的增刪改查。是java設計者給咱們提供的便利。下面介紹經常使用集合類及其經常使用方法。
在咱們調用集合類以前首先要引入一個包,java.util.*;由於咱們全部的集合類基本上都在這個包下。
一、List結構的集合類
i. ArrayList集合類【如下方法經過ArrayList實例進行調用】
ArrayList的使用案例以下
Clerk類-》屬性name、age、salary
實現增刪改查長度操做
ii. LinkedList集合類【如下方法需經過LinkedList實例進行調用】
方法一、addFirst(Object object);後加的對象在前面
方法二、addLast(Object object);後加的在後面
方法三、removeFirst();刪除第一個
方法四、removeLast();刪除最後一個
iii. Vector集合類
iv. Stack集合類
Stack集合類的add()方法是往前面加的。和ArrayList不一樣
案例:如今咱們能夠經過以上集合實現員工管理系統。
將集合就離不開>>範型<<
記住集合使用泛型與不使用泛型有區別,若是在容器後面不註明尖括號<>,即不適用的範型的話建立的實例是object類型的,須要強制類型轉換,而修飾後返回的就是咱們須要的類型。
拓展:對比——ArrayList和Vector的區別
ArrayList和Vector的區別
ArrayList與Vector都是Java的集合類,均可以用來存放Java對象,這是他們的相同點,可是他們也有區別。
1、同步性
Vector是同步的。這個類中的一些方法保證了Vector中的對象是線程安全的。而ArrayList則是異步的,所以ArrayList中的對象並非線程安全的。所以同步的要求會影響執行的效率,多以若是你不須要線程安全的集合那麼使用ArrayList是一種很好的選擇,這樣能夠避免因爲同步帶來的沒必要要的性能開銷。
2、數據增加
從內部實現機制來說ArrayList和Vector都是使用數組(Array)來控制集合中的對象。當你向這兩種類型中增長元素的時候,若是元素的數目超出了內部數組目前的長度,他們都須要拓展內部數組的長度,Vector缺省狀況下自動增加原來的一倍的數組長度,ArrayList是原來的50%,因此最後你得到的這個集合所佔的空間老是比你須要的要大。因此若是你要在集合中保存大量的數據那麼使用Vector有一些優點,由於你能夠經過設置集合的初始化大小來避免沒必要要的資源開銷。
二、Map結構的集合類
Map結構的集合類與上面的List結構的集合類不同,由於Map中存儲的是鍵值對。
i. HashMap集合類
增長方法:put(key,Object value);
是否包含某鍵值:containsKey(key);
查找HashMap中的對象:getObject(key);
注意,Map集合中不容許有相同的兩個鍵存儲在該集合中。若是後來再加入相同的鍵加入集合,後者的值會覆蓋前者的值。
HashMap的遍歷:Iterator 迭代器,由於HashMap它存放了多少它本身是不知道的,而Iterator迭代器能夠探測。遍歷方式以下
//這一步將hm中的鍵所有返回
Iterator it=hm.keySet().iterator();
//hashNext返回一個boolean,用於判斷還有沒有下一個
while(it.hasNext()){
//取出key
String key=it.next().toString();
//經過key取出value
***=hm.get(key);
}
ii.Hashtable集合類
基本上能夠這麼理解,Hashtable和HashMap的用法基本上都同樣。
拓展:HashMap和Hashtable的區別。
HashMap與Hashtable都是java的集合類,均可以用來存放java對象,這是他們的相同點,可是他們也有區別:
1、歷史緣由:
Hashtable是基於陳舊的Dictionary類的,HashMap是Java 1.2引進的Map接口的一個實現。
2、同步性
Hashtable時同步的。這個類中的一些方法保證了Hashtable中的對象是線程安全的。而HashMap則是異步的,所以HashMap中的對象並非線程安全的。由於同步的要求會影響執行的效率,因此若是不須要線程安全的集合,那麼使用HashMap是一個很好的選擇,這樣能夠避免因爲同步帶來的沒必要要的性能開銷,從而提升效率。
3、值
HashMap可讓你將空值做爲一個表的條目的key或value,可是Hashtable是不能放入空值的(null)
------------------------------------------------
集合框架--總結
Java的設計者們給咱們提供了這些集合類,在後面編程中是至關有用的,具體何時用什麼集合,要根據咱們剛纔分析的集合異同來選取。
總結爲如下幾點
①、若是要求線程安全使用Vector、Hashtable
②、若是不要求線程安全使用ArrayList、HashMap、LinkedList
③、若是要求鍵值對,則使用HashMap,Hashtable
④、若是數據量很大,使用Vector
要求:用合適的集合完成上方的薪資管理系統。
練習題:目的聯繫對List、Map、Set的不一樣
練習1、大聖準備帶着小猴去操練,可是隊伍實在太不成隊伍,很是散漫,唐僧建議用Java裏面的容器和和接口去裝小猴,從新組織隊伍。
第一關、設計程序使用List接口來容納10只小猴
第二關、用Set來裝在10只猴,對他們可執行查找和替換功能
第三關、用Map接口來裝載10只小猴,對他們執行最快的查找和替換功能
練習2、若是作一個詞典(英漢),若是不使用數據庫,你會怎樣實現。
第十四講 範型
目標:充分理解什麼是泛型
1. 概述
泛型在java中有很重要的地位,在面向對象編程及各類設計模式中有很是普遍的應用。
什麼是泛型?爲何要使用泛型?
泛型,即「參數化類型」。一提到參數,最熟悉的就是定義方法時有形參,而後調用此方法時傳遞實參。那麼參數化類型怎麼理解呢?
顧名思義,就是將類型由原來的具體的類型參數化,相似於方法中的變量參數,此時類型也定義成參數形式(能夠稱之爲類型形參),
而後在使用/調用時傳入具體的類型(類型實參)。
泛型的本質是爲了參數化類型(在不建立新的類型的狀況下,經過泛型指定的不一樣類型來控制形參具體限制的類型)。也就是說在泛型使用過程當中,
操做的數據類型被指定爲一個參數,這種參數類型能夠用在類、接口和方法中,分別被稱爲泛型類、泛型接口、泛型方法。
2. 案例引入
List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);
for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
Log.d("泛型測試","item = " + item);
}
毫無疑問,程序的運行結果會以崩潰結束: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
ArrayList能夠存聽任意類型,例子中添加了一個String類型,添加了一個Integer類型,再使用時都以String的方式使用,所以程序崩潰了。爲了解決相似這樣的問題(在編譯階段就能夠解決),泛型應運而生。
咱們將第一行聲明初始化list的代碼更改一下,編譯器會在編譯階段就可以幫咱們發現相似這樣的問題。
List<String> arrayList = new ArrayList<String>();
...
3. 特性
泛型只在編譯階段有效。看下面的代碼:
List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();
Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();
if(classStringArrayList.equals(classIntegerArrayList)){
Log.d("泛型測試","類型相同");
}
輸出結果:D/泛型測試: 類型相同。
經過上面的例子能夠證實,在編譯以後程序會採起去泛型化的措施。也就是說Java中的泛型,只在編譯階段有效。在編譯過程當中,正確檢驗泛型結果後,會將泛型的相關信息擦出,而且在對象進入和離開方法的邊界處添加類型檢查和類型轉換的方法。也就是說,泛型信息不會進入到運行時階段。
對此總結成一句話:泛型類型在邏輯上看以當作是多個不一樣的類型,實際上都是相同的基本類型。
4. 泛型的使用
泛型有三種使用方式,分別爲:泛型類、泛型接口、泛型方法
4.3 泛型類
泛型類型用於類的定義中,被稱爲泛型類。經過泛型能夠完成對一組類的操做對外開放相同的接口。最典型的就是各類容器類,如:List、Set、Map。
泛型類的最基本寫法(這麼看可能會有點暈,會在下面的例子中詳解):
class 類名稱 <泛型標識:能夠隨便寫任意標識號,標識指定的泛型的類型>{
private 泛型標識
一個最普通的泛型類:
輸出結果
12-27 09:20:04.432 13063-13063/? D/泛型測試: key is 123456
12-27 09:20:04.432 13063-13063/? D/泛型測試: key is key_vlaue
定義的泛型類,就必定要傳入泛型類型實參麼?並非這樣,在使用泛型的時候若是傳入泛型實參,則會根據傳入的泛型實參作相應的限制,此時泛型纔會起到本應起到的限制做用。若是不傳入泛型類型實參的話,在泛型類中使用泛型的方法或成員變量定義的類型能夠爲任何的類型。
看一個例子:
Generic generic = new Generic("111111");
Generic generic1 = new Generic(4444);
Generic generic2 = new Generic(55.55);
Generic generic3 = new Generic(false);
Log.d("泛型測試","key is " + generic.getKey());
Log.d("泛型測試","key is " + generic1.getKey());
Log.d("泛型測試","key is " + generic2.getKey());
Log.d("泛型測試","key is " + generic3.getKey());
輸出結果
D/泛型測試: key is 111111
D/泛型測試: key is 4444
D/泛型測試: key is 55.55
D/泛型測試: key is false
注意:
泛型的類型參數只能是類類型,不能是簡單類型。 不能對確切的泛型類型使用instanceof操做
。以下面的操做是非法的,編譯時會出錯。 if(ex_num instanceof Generic<Number>){ }
4.4 泛型接口
泛型接口與泛型類的定義及使用基本相同。泛型接口常被用在各類類的生產器中,能夠看一個例子:
當實現泛型接口的類,未傳入泛型實參時:
當實現泛型接口的類,傳入泛型實參時:
4.5 泛型通配符
咱們知道Ingeter是Number的一個子類,同時在特性章節中咱們也驗證過Generic<Ingeter>與Generic<Number>其實是相同的一種基本類型。那麼問題來了,在使用Generic<Number>做爲形參的方法中,可否使用Generic<Ingeter>的實例傳入呢?在邏輯上相似於Generic<Number>和Generic<Ingeter>是否能夠當作具備父子關係的泛型類型呢?
爲了弄清楚這個問題,咱們使用Generic<T>這個泛型類繼續看下面的例子:
public void showKeyValue1(Generic<Number> obj){
Log.d("泛型測試","key value is " + obj.getKey());
}
Generic<Integer> gInteger = new Generic<Integer>(123);
Generic<Number> gNumber = new Generic<Number>(456);
showKeyValue(gNumber);
經過提示信息咱們能夠看到Generic<Integer>不能被看做爲`Generic<Number>的子類。由此能夠看出:同一種泛型能夠對應多個版本(由於參數類型是不肯定的),不一樣版本的泛型類實例是不兼容的。
回到上面的例子,如何解決上面的問題?總不能爲了定義一個新的方法來處理Generic<Integer>類型的類,這顯然與java中的多臺理念相違背。所以咱們須要一個在邏輯上能夠表示同時是Generic<Integer>和Generic<Number>父類的引用類型。由此類型通配符應運而生。
咱們能夠將上面的方法改一下:
public void showKeyValue1(Generic<?> obj){
Log.d("泛型測試","key value is " + obj.getKey());
}
類型通配符通常是使用?代替具體的類型實參,注意了,此處’?’是類型實參,而不是類型形參 。重要說三遍!此處’?’是類型實參,而不是類型形參 ! 此處’?’是類型實參,而不是類型形參 !再直白點的意思就是,此處的?和Number、String、Integer同樣都是一種實際的類型,能夠把?當作全部類型的父類。是一種真實的類型。
能夠解決當具體類型不肯定的時候,這個通配符就是 ? ;當操做類型時,不須要使用類型的具體功能時,只使用Object類中的功能。那麼能夠用 ? 通配符來表未知類型。
4.6 泛型方法
在java中,泛型類的定義很是簡單,可是泛型方法就比較複雜了。
尤爲是咱們見到的大多數泛型類中的成員方法也都使用了泛型,有的甚至泛型類中也包含着泛型方法,這樣在初學者中很是容易將泛型方法理解錯了。
泛型類,是在實例化類的時候指明泛型的具體類型;泛型方法,是在調用方法的時候指明泛型的具體類型 。
Object obj = genericMethod(Class.forName("com.test.test"));
4.6.1 泛型方法的基本用法
光看上面的例子有的同窗可能依然會很是迷糊,咱們再經過一個例子,把我泛型方法再總結一下。
public class GenericTest {
4.6.2 類中的泛型方法
固然這並非泛型方法的所有,泛型方法能夠出現雜任何地方和任何場景中使用。可是有一種狀況是很是特殊的,當泛型方法出如今泛型類中時,咱們再經過一個例子看一下
public class GenericFruit {
class Fruit{
@Override
public String toString() {
return "fruit";
}
}
class Apple extends Fruit{
@Override
public String toString() {
return "apple";
}
}
class Person{
@Override
public String toString() {
return "Person";
}
}
class GenerateTest<T>{
public void show_1(T t){
System.out.println(t.toString());
}
4.6.3 泛型方法與可變參數
再看一個泛型方法和可變參數的例子:
public <T> void printMsg( T... args){
for(T t : args){
Log.d("泛型測試","t is " + t);
}
}
printMsg("111",222,"aaaa","2323.4",55.55);
4.6.4 靜態方法與泛型
靜態方法有一種狀況須要注意一下,那就是在類中的靜態方法使用泛型:靜態方法沒法訪問類上定義的泛型;若是靜態方法操做的引用數據類型不肯定的時候,必需要將泛型定義在方法上。
即:若是靜態方法要使用泛型的話,必須將靜態方法也定義成泛型方法 。
public class StaticGenerator<T> {
....
....
4.6.5 泛型方法總結
泛型方法能使方法獨立於類而產生變化,如下是一個基本的指導原則:
不管什麼時候,若是你能作到,你就該儘可能使用泛型方法。也就是說,若是使用泛型方法將整個類泛型化,
那麼就應該使用泛型方法。另外對於一個static的方法而已,沒法訪問泛型類型的參數。
因此若是static方法要使用泛型能力,就必須使其成爲泛型方法。
4.6.6 泛型上下邊界
在使用泛型的時候,咱們還能夠爲傳入的泛型類型實參進行上下邊界的限制,如:類型實參只准傳入某種類型的父類或某種類型的子類。
爲泛型添加上邊界,即傳入的類型實參必須是指定類型的子類型
public void showKeyValue1(Generic<? extends Number> obj){
Log.d("泛型測試","key value is " + obj.getKey());
}
Generic<String> generic1 = new Generic<String>("11111");
Generic<Integer> generic2 = new Generic<Integer>(2222);
Generic<Float> generic3 = new Generic<Float>(2.4f);
Generic<Double> generic4 = new Generic<Double>(2.56);
若是咱們把泛型類的定義也改一下:
public class Generic<T extends Number>{
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey(){
return key;
}
}
再來一個泛型方法的例子:
經過上面的兩個例子能夠看出:泛型的上下邊界添加,必須與泛型的聲明在一塊兒 。
4.7 關於泛型數組要提一下
看到了不少文章中都會提起泛型數組,通過查看sun的說明文檔,在java中是」不能建立一個確切的泛型類型的數組」的。 也就是說下面的這個例子是不能夠的:
List<String>[] ls = new ArrayList<String>[10];
而使用通配符建立泛型數組是能夠的,以下面這個例子:
List<?>[] ls = new ArrayList<?>[10];
這樣也是能夠的:
List<String>[] ls = new ArrayList[10];
下面使用Sun的一篇文檔的一個例子來講明這個問題:
List<String>[] lsa = new List<String>[10];
這種狀況下,因爲JVM泛型的擦除機制,在運行時JVM是不知道泛型信息的,因此能夠給oa[1]賦上一個ArrayList而不會出現異常,
可是在取出數據的時候卻要作一次類型轉換,因此就會出現ClassCastException,若是能夠進行泛型數組的聲明,
上面說的這種狀況在編譯期將不會出現任何的警告和錯誤,只有在運行時纔會出錯。
而對泛型數組的聲明進行限制,對於這樣的狀況,能夠在編譯期提示代碼有類型安全問題,比沒有任何提示要強不少。
下面採用通配符的方式是被容許的:數組的類型不能夠是類型變量,除非是採用通配符的方式,由於對於通配符的方式,最後取出數據是要作顯式的類型轉換的。
List<?>[] lsa = new List<?>[10];
5. 最後
本文中的例子主要是爲了闡述泛型中的一些思想而簡單舉出的,並不必定有着實際的可用性。另外,一提到泛型,相信你們用到最多的就是在集合中,其實,在實際的編程過程當中,本身能夠使用泛型去簡化開發,且能很好的保證代碼質量。
1、範型的產生歷史
一、範型是Java SE1.5的新特性,範型的本質是參數化類型,也就是說所操做的數據類型被指定爲。這種參數類型能夠用在類、接口和方法的建立中,分別稱爲範型類、範型接口、範型方法。
二、Java語言引入範型的好處是安全簡單。
Java SE1.5以前,沒有範型的狀況下,是經過對範型Object的引用實現參數的「任意化」,「任意化」的缺點是要進行顯式的強制類型轉換,而這種轉換是要求開發者對實際參數類型能夠預知的狀況下進行的。對於強制類型轉換錯誤的狀況,編譯器可能不提示錯誤,可是在運行的時候出現異常,這是一個安全隱患。
範型的好處是在編譯的時候檢查類型安全,而且全部的強制轉換都是自動和隱式的,提升代碼的重用率。
2、案例:定義狗類
在測試類建立ArrayList實例,把狗加進去,注意說明<>含義。若是<>不加那麼在從ArrayList中再取出該對象的話取出的是Object,若是在定義一隻貓,加入ArrayList後再從中取出,強轉爲狗類交給狗的實例,可是編譯器不會報錯。
關於什麼是範型,咱們能夠先定義一個範型類,體會一下。它體現了Java的一種反射機制。源碼以下
class Gen<T>{
private T o;
//構造函數
public Gen(T a){
o=a;
}
//獲得T的類型名稱
public void showTypeName(){
System.out.println("類型是"+o.getClass().getName());
}
}
下方是測試類中的代碼
//獲得T這個類型的名字
Gen<String> str=new Gen<String>("jjaljglajlkgj");
str.showTypeName();
//經過反射機制獲得這個T類型的不少信息(好比方法名)
Method[] m=o.getClass().getDeclaredMethods();
//打印
for(int i=0;i<m.length;i++)
{
System.out.println(m[i].getName());
}
經過反射機制,咱們能夠獲得T這個類型的方法個數以及成員函數的函數名等。
就像咱們寫了同一個類,可是咱們這個類的可用範圍很廣,在不一樣的而類型之間均可以使用,可是咱們不可能爲每一個類型都寫一個這樣的類,因此咱們就能夠經過範型解決這個問題,提升了代碼的複用率。
3、範型的優勢
使用範型有下面幾個優勢
①、類型安全
②、向後兼容
③、層次清晰
④、性能較高,用GJ(範型代碼)編寫的代碼能夠爲Java編譯器和虛擬機帶來更多的類型信息,這些信息爲Java程序作進一步優化提供條件(如自動類型轉換和類型檢測等)。範型的反射機制爲範型在類型轉換的時候提供了參考依據。簡單說泛型的反射機制就是經過獲取以參數形式傳過來的類型中的方法以繼方法中的返回值和方法名等來進行自動類型轉換時的檢測。
第十五講 異常
定義:當程序出現控制外部環境問題(如用戶提供的文件不存在,文件損壞,或者網絡不可用),Java就會用異常對象來描述。
Java中用2中方法處理異常
一、在發生異常的地方直接處理異常
二、將異常拋給調用者,讓調用者去處理
1、異常分類
①、編譯異常(又稱檢查性異常)java.lang.Exception
當程序尚未運行的時候編譯器就已發現錯誤,此時編程者就須要處理。
②、運行異常java.lang.RuntimeException
當程序在運行的過程當中發生異常,如數組訪問越界。再如int a=4/0;編寫過程當中都不會報錯。
③、ERROR java.lang.Error
這種錯誤是最難解決的,但通常更可能源於環境問題,如內存耗盡,殺毒軟件的阻礙等。
頂層時java.lang.Throwable類,檢查性異常,運行期異常,錯誤都是這個類的子孫類。java.lang.Error和java.lang.Exception都繼承自java.lang.Throwable,而java.lang.RuntimeException繼承自java.lang.Exception
2、如何處理異常
一、try{...}catch(Exception e1){.....}catch(Exception e2){...}...
程序運行產生異常時,將從程序異常發生點中斷程序並拋出異常信息。也就是能夠捕獲而後處理。
在try中編寫可能發生異常的代碼塊,經過catch進行捕獲並處理。若是有多個catch語句,就會在發生異常的catch捕獲信息。
二、finally
若是把finally塊置try......catch.....語句後,finally快通常都會獲得執行,它至關於一個萬能的保險,即便前面的try塊發生異常,而又沒有對應的異常的catch塊,finally塊將立刻執行。
如下情形,finally塊將不會被執行:
(1)finally塊發生了異常
(2)程序全部線程死亡
(3)在前面的代碼中使用了System.exit();
(4)關閉CPU
finally語句塊不管前面有沒有發生異常都將執行finally語句塊。
try.....catch...finally語句中catch是非必須的。可是必須有try和另兩個中的至少一個。
三、將異常拋給調用者【throws Exception】
若是一個類(A)調用另外一個類(B)的實例執行某個方法,而被調用的類(B)的那個方法並無處理可能出現的異常,那麼就由調用那個方法的調用者(A)進行處理。不建議這樣使用,由於會致使最後咱們在排錯的過程當中十分艱難。
----我是內容與擴充的分割線----
內容擴充模塊
指望在之後的編碼中BUG與我無緣---佛祖保佑無BUG源碼---從網上扒來的,非本人原創。
// _ooOoo_
// o8888888o
// 88" . "88
// (| -_- |)
// O\ = /O
// ____/`---'\____
// . ' \\| |// `.
// / \\||| : |||// \
// / _||||| -:- |||||- \
// | | \\\ - /// | |
// | \_| ''\---/'' | |
// \ .-\__ `-` ___/-. /
// ___`. .' /--.--\ `. . __
// ."" '< `.___\_<|>_/___.' >'"".
// | | : `- \`.;`\ _ /`;.`/ - ` : | |
// \ \ `-. \_ __\ /__ _/ .-` / /
// ======`-.____`-.___\_____/___.-`____.-'======
// `=---='
//
// .............................................
// 佛祖保佑 永無BUG
// 佛曰:
// 寫字樓裏寫字間,寫字間里程序員;
// 程序人員寫程序,又拿程序換酒錢。
// 酒醒只在網上坐,酒醉還來網下眠;
// 酒醉酒醒日復日,網上網下年復年。
// 希望老死電腦間,不肯鞠躬老闆前;
// 奔馳寶馬貴者趣,公交自行程序員。
// 別人笑我忒瘋癲,我笑本身命太賤;
// 不見滿街漂亮妹,哪一個歸得程序員?
1、數在內存中的存儲
1、原碼,反碼,補碼
在 C 語言裏數據類型有 有符號數 和 無符號數 之分。
只有有符號數纔有原碼,反碼,補碼的概念,由於有符號數的最高位表示正負。
而無符號數無論怎麼樣都是表示正數,因此它的原碼,反碼,補碼都是同樣。
在計算機中爲了計算方便,全部數據都是以補碼的形式存儲的。
由於這樣減法運算也能夠按照加法來運算,這就很巧妙了。
舉個例子:0,-0,21.65625,-21.65625的原碼,反碼,補碼分別是多少?
(全部的數據類型均可以這樣運算,爲了方便,這裏僅展現計算方式,不涉及相關數據大小)
十進制數 |
原碼 |
反碼 |
補碼 |
0 |
0000 0000 0000 0000 |
0000 0000 0000 0000 |
0000 0000 0000 0000 |
-0 |
1000 0000 0000 0000 |
1111 1111 1111 1111 |
0000 0000 0000 0000 |
18 |
0000 0000 0001 0010 |
0000 0000 0001 0010 |
0000 0000 0001 0010 |
-18 |
1000 0000 0001 0010 |
1111 1111 1110 1101 |
1111 1111 1110 1110 |
21.65625 |
0001 0101.1010 1000 |
0001 0101.1010 1000 |
0001 0101.1010 1000 |
-21.65625 |
1001 0101.1010 1000 |
1110 1010.0101 0111 |
1110 1010.0101 1000 |
(從這個表中能夠清楚的看到全部的加減法運算均可以用加法來進行運算)
原碼,反碼,補碼的運算方式:
[原碼]:計算機中將一個數字轉換爲二進制,並在其最高位加上符號的一種表示方法。
[反碼]:根據表示規定,正數的反碼就是自己,而負數的反碼,除符號位外,其他位依次取反。
[補碼]:根據表示規定,正數的補碼就是自己,而負數的補碼,是在其反碼的末位加1。
舉個例子:
正數 5 和 負數 5
5 原碼:00000101 -5 原碼:10000101
反碼:00000101 -5 反碼:11111010
補碼:00000101 -5 補碼:11111011
2、邏輯運算
邏輯運算符 |
名稱 |
說明 |
<< |
左移 |
左移n位表明乘2^n |
>> |
右移 |
右移n位表明除2^n |
| |
位或 |
即全爲0則爲0 |
& |
位與 |
即全爲1則爲1 |
~ |
位非 |
即 ~1 得 0,~0 得 1 |
^ |
位異或 |
即相同爲 0,不一樣爲 1 |
2、進制換算
3、float和double的取值範圍和表示
1、存儲結構介紹
C 語言和C#語言中,對於浮點類型的數據採用單精度類型(float)和雙精度類型(double)來存儲,float數據佔用32bit, double數據佔用64bit,咱們在聲明一個變量float f= 2.25f的時候,是如何分配內存的呢?若是胡亂分配,那世界豈不是亂套了麼,其實不管是float仍是double在存儲方式上都是聽從IEEE的規範的,float聽從的是IEEE R32.24 ,而double 聽從的是R64.53。
不管是單精度仍是雙精度在存儲中都分爲三個部分:
1.符號位(Sign) : 0表明正,1表明爲負
2.指數位(Exponent):用於存儲科學計數法中的指數數據,而且採用移位存儲
3.尾數部分(Mantissa):尾數部分
Float的存儲方式

double的存儲方式

float和double的精度是由尾數的位數來決定的。
浮點數在內存中是按科學計數法來存儲的,其整數部分始終是一個隱含着的「1」,因爲它是不變的,故不能對精度形成影響。
float:2^23 = 8388608,共七位,意味着最多能有7位有效數字,但絕對能保證的爲6位,也即float的精度爲6~7位有效數字;
double:2^52 = 4503599627370496,一共16位,同理,double的精度爲15~16位。
float的範圍爲-2^128 ~ +2^128,也即-3.40E+38 ~ +3.40E+38;
double的範圍爲-2^1024 ~ +2^1024,也即-1.79E+308 ~ +1.79E+308
2、符號位,階碼,尾數
在計算機內部實數都是以 (符號位-階碼-尾數) 的形式表示的。
一個 float 型實數在內存中佔 4byte,即 32bit。
從低位到高位依次叫 第0位 到 第31位。這 32位 能夠分紅 3個部分:
一、符號位(第31位) --- 0表示正數,1表示負數。
二、階碼(第30位 到 第 23位) ---
這8個二進制位表示該實數轉化爲規格後的二進制實數後的指數與127之和(即所謂的階碼)。(127即所謂的偏移量)
規格化後的二進制實數的指數只能在 -127 到 +127 之間。
三、尾數(餘下的23位) --- 即小數點後的23位。
double 類型:(8byte,即 64bit)
一、符號位(第31位) --- 0表示正數,1表示負數。
二、階碼(第30位 到 第20位)。規格化後的二進制實數的指數只能在 -1023 到 +1023 之間。
三、尾數(餘下的52位) --- 即小數點後的52位。
舉個例子(float類型):1.5,-1.5 符號位,階碼,尾數,及在計算機內存中的表示(16進制)
十進制數 |
二進制實數 |
符號位 |
階碼 |
尾數 |
內存中的表示(2進制) |
內存中的表示(16進制) |
0.75 |
1.1*2^-1 |
0 |
0111 1110 |
1 |
0011 1111 0100 0000 0000 0000 0000 0000 |
3F 40 00 00 |
-0.75 |
-1.1*2^-1 |
1 |
0111 1110 |
1 |
1011 1111 0100 0000 0000 0000 0000 0000 |
BF 40 00 00 |
1.5 |
1.1*2^0 |
0 |
0111 1111 |
1 |
0011 1111 1100 0000 0000 0000 0000 0000 |
3F C0 00 00 |
-1.5 |
-1.1*2^0 |
1 |
0111 1111 |
1 |
1011 1111 1100 0000 0000 0000 0000 0000 |
BF C0 00 00 |
3.0 |
1.1*2^1 |
0 |
1000 0000 |
1 |
0100 0000 0100 0000 0000 0000 0000 0000 |
40 40 00 00 |
-3.0 |
-1.1*2^1 |
1 |
1000 0000 |
1 |
1100 0000 0100 0000 0000 0000 0000 0000 |
C0 40 00 00 |
5.625 |
1.01101*2^2 |
0 |
1000 0001 |
01101 |
0100 0000 1011 0100 0000 0000 0000 0000 |
40 B4 00 00 |
-5.625 |
-1.01101*2^2 |
1 |
1000 0001 |
01101 |
1100 0000 1011 0100 0000 0000 0000 0000 |
C0 B4 00 00
|
4、Java 基本數據類型boolean在內存中到底佔用多少字節
爲何要問這個問題,首先在Java中定義的八種基本數據類型中,boolean類型沒有給出具體的佔用字節數,由於對虛擬機來講根本就不存在 boolean 這個類型,boolean類型在編譯後會使用其餘數據類型來表示,那boolean類型究竟佔用多少個字節?答案五花八門,基本有如下幾種:
一、1個bit
理由是boolean類型的值只有true和false兩種邏輯值,在編譯後會使用1和0來表示,這兩個數在內存中只須要1位(bit)便可存儲,位是計算機最小的存儲單位。
二、1個字節
理由是雖然編譯後1和0只需佔用1位空間,但計算機處理數據的最小單位是1個字節,1個字節等於8位,實際存儲的空間是:用1個字節的最低位存儲,其餘7位用0填補,若是值是true的話則存儲的二進制爲:0000 0001,若是是false的話則存儲的二進制爲:0000 0000。
三、4個字節
在Java虛擬機中沒有任何供boolean值專用的字節碼指令,Java語言表達式所操做的boolean值,在編譯以後都使用Java虛擬機中的int數據類型來代替,而boolean數組將會被編碼成Java虛擬機的byte數組,每一個元素佔8位
顯然第三條是更準確的說法,那虛擬機爲何要用int來代替boolean呢?爲何不用byte或short,這樣不是更節省內存空間嗎。大多數人都會很天然的這樣去想,我一樣也有這個疑問,通過查閱資料發現,使用int的緣由是,對於當下32位的處理器(CPU)來講,一次處理數據是32位(這裏不是指的是32/64位系統,而是指CPU硬件層面),具備高效存取的特色。
能夠看出,boolean類型沒有給出精確的定義,《Java虛擬機規範》給出了boolean類型佔4個字節,和boolean數組1個字節的定義,具體還要看虛擬機實現是否按照規範來,因此1個字節、4個字節都是有可能的。這實際上是運算效率和存儲空間之間的博弈,二者都很是的重要。
原文:https://blog.csdn.net/dingpf1209/article/details/80259500
5、ASCII美國信息交換標準代碼
因爲計算機只認識二進制數,即0或1,因此每個字符都被用一種編碼方式轉化成二進制存儲在計算機中,這種編碼方式就是ASCII

ASCII(American Standard Code for Information Interchange,美國信息交換標準代碼)是基於
拉丁字母的一套電腦編碼系統,主要用於顯示現代
英語和其餘
西歐語言。它是現今最通用的單
字節
編碼系統,並等同於
國際標準ISO/IEC 646。
[1]
請注意,ASCII是American Standard Code for Information Interchange縮寫,而不是ASCⅡ(羅馬數字2),有不少人在這個地方產生誤解。
從上圖能夠看出,每個字符都被一種編碼表明着,因爲在表中採用二進制很差閱讀,因此上表展現的是與二進制等值的十進制數與各字符的對照關係。
因此字符在計算機中存儲本質就是把字符轉換成數字在計算機中存儲。
6、對《數據結構》從新仔細學習
7、自加與自減的注意事項
++:自增
a.自增運算符只能做用於變量,做用是讓該變量的值增長1
規律:
若是++變量和變量++是單獨一個式子(在編程語言中,一句話是以英文的;分號結尾的) ,那麼++在前和在後沒有任何區別
如: ++i;和i++;的結果是同樣的,都是在i的原來數值上加1。
若是++變量和變量++是混合式子,那麼++在前,先加後用
那麼++在後,先用後加;
好比:
int j = 10;
//int k = j++; // ++在後,先用後加
int k = ++j; // ++在前,先加後用
System.out.println(k); // 11
System.out.println(j); // 11
自減--的狀況和自加相同,再也不贅述。
規律:
若是--變量和變量--是單獨一個式子 ,那麼--在前和在後沒有任何區別
若是--變量和變量--是混合式子,那麼--在前,先減後用
那麼--在後,先用後減
再深刻:特別注意請看代碼
/*
* 做者:Alvin
* 功能:前、後自加和自減的危險使用方法
* 時間:2019年2月22日10:33:38
* */
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
//定義兩個變量
int i=0;
//==================對變量i進行操做===========
/*
* 下方報錯,緣由是和棧的進出順序有關若是,在java
*能夠這麼認爲,自加和自減運算在進行操做的時候都會
*返回一個計算後的結果常量並改變被操做變量的值,而
*常量又不能夠進行自加和自減操做因此會報錯。
* */
--(i++);
System.out.println(i);
}
}
拓展閱讀: j=j++和j=++j; 的區別
8、i+=1;、++i;和i=i+1;這三個表達式的比較
咱們先看一下在編譯器中的狀況。
/*
* 做者:Alvin
* 功能:i+=1;、++i;和i=i+1;這三個表達式的比較
* 時間:2019年2月22日09:58:08
* */
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
//定義兩個變量
byte c=0;//此處的byte、short變量結果相同,char不考慮比較沒有意義
int b=0;
//==================對變量c進行操做===========
//自加運算
c++;
System.out.println(c);
//符合賦值運算
c+=1;
c+=c;
System.out.println(c);
//數學運算
c=c+1;
System.out.println(c);
//==================對變量b進行操做===========
//自加運算
b++;
System.out.println(b);
//符合賦值運算
b+=1;
b+=b;
System.out.println(b);
//數學運算
b=b+1;
System.out.println(b);
}
}
在開發工具中會報錯。

在上面的結果中能夠看到,在23行已經報錯,其餘代碼正常。數學運算符中的+、-、*、/、%存在着類型的自動轉換,而++和+=運算符進行運算的時候,編譯器存在着對變量和常量的運算的優化
以+=爲例【++的狀況和此相似,再也不贅述。】
+=運算符
int a = 10;
a += 5; 等價於 a = a + 5;
short s = 10;
s+=5; 等價於 s = (short)(s + 5);
System.out.println(s);
byte b = 10;
b+=5;等價於 b = (byte)(b + 5)
9、enum類型講解
10、Java中的關鍵字
Java中的關鍵字
關鍵字 |
含義 |
abstract |
代表類或者成員方法具備抽象屬性 |
assert |
斷言,用來進行程序調試 |
boolean |
基本數據類型之一,布爾類型 |
break |
提早跳出一個塊 |
byte |
基本數據類型之一,字節類型 |
case |
用在switch語句之中,表示其中的一個分支 |
catch |
用在異常處理中,用來捕捉異常 |
char |
基本數據類型之一,字符類型 |
class |
聲明一個類 |
const |
保留關鍵字,沒有具體含義 |
continue |
回到一個塊的開始處 |
default |
默認,例如,用在switch語句中,代表一個默認的分支 |
do |
用在do-while循環結構中 |
double |
基本數據類型之一,雙精度浮點數類型 |
else |
用在條件語句中,代表當條件不成立時的分支 |
enum |
枚舉 |
extends |
代表一個類型是另外一個類型的子類型,這裏常見的類型有類和接口 |
final |
用來講明最終屬性,代表一個類不能派生出子類,或者成員方法不能被覆蓋,或者成員域的值不能被改變,用來定義常量 |
finally |
用於處理異常狀況,用來聲明一個基本確定會被執行到的語句塊 |
float |
基本數據類型之一,單精度浮點數類型 |
for |
一種循環結構的引導詞 |
goto |
保留關鍵字,沒有具體含義 |
if |
條件語句的引導詞 |
implements |
代表一個類實現了給定的接口 |
import |
代表要訪問指定的類或包 |
instanceof |
用來測試一個對象是不是指定類型的實例對象 |
int |
基本數據類型之一,整數類型 |
interface |
接口 |
long |
基本數據類型之一,長整數類型 |
native |
用來聲明一個方法是由與計算機相關的語言(如C/C++/FORTRAN語言)實現的 |
new |
用來建立新實例對象 |
package |
包 |
private |
一種訪問控制方式:私用模式 |
protected |
一種訪問控制方式:保護模式 |
public |
一種訪問控制方式:共用模式 |
return |
從成員方法中返回數據 |
short |
基本數據類型之一,短整數類型 |
static |
代表具備靜態屬性 |
strictfp |
用來聲明FP_strict(單精度或雙精度浮點數)表達式遵循IEEE 754算術規範 [1] |
super |
代表當前對象的父類型的引用或者父類型的構造方法 |
switch |
分支語句結構的引導詞 |
synchronized |
代表一段代碼須要同步執行 |
this |
指向當前實例對象的引用 |
throw |
拋出一個異常 |
throws |
聲明在當前定義的成員方法中全部須要拋出的異常 |
transient |
聲明不用序列化的成員域 |
try |
嘗試一個可能拋出異常的程序塊 |
void |
聲明當前成員方法沒有返回值 |
volatile |
代表兩個或者多個變量必須同步地發生變化 |
while |
用在循環結構中 |
如下采用C/C++語言的知識進行講解
1、預備知識—程序的內存分配
一個由c/C++編譯的程序佔用的內存分爲如下幾個部分
一、棧區(stack)— 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧。
二、堆區(heap) — 通常由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。注意它與數據結構中的堆是兩回事,分配方式卻是相似於鏈表,呵呵。
三、全局區(靜態區)(static)—,全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另外一塊區域。 - 程序結束後有系統釋放
四、文字常量區—常量字符串就是放在這裏的。 程序結束後由系統釋放
五、程序代碼區—存放函數體的二進制代碼。
2、例子程序
這是一個前輩寫的,很是詳細
//main.cpp
int a = 0; 全局初始化區
char *p1; 全局未初始化區
main()
{
int b; 棧
char s[] = "abc"; 棧
char *p2; 棧
char *p3 = "123456"; 123456\0在常量區,p3在棧上。
static int c =0; 全局(靜態)初始化區
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得來得10和20字節的區域就在堆區。
strcpy(p1, "123456"); 123456\0放在常量區,編譯器可能會將它與p3所指向的"123456"優化成一個地方。
}
3、堆和棧的理論知識
3.1申請方式
stack:
由系統自動分配。 例如,聲明在函數中一個局部變量 int b; 系統自動在棧中爲b開闢空間
heap:
須要程序員本身申請,並指明大小,在c中malloc函數
如p1 = (char *)malloc(10);
在C++中用new運算符
如p2 = (char *)malloc(10);
可是注意p一、p2自己是在棧中的。
3.2 申請後系統的響應
棧:只要棧的剩餘空間大於所申請空間,系統將爲程序提供內存,不然將報異常提示棧溢出。
堆:首先應該知道操做系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,
會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,而後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語句才能正確的釋放本內存空間。另外,因爲找到的堆結點的大小不必定正好等於申請的大小,系統會自動的將多餘的那部分從新放入空閒鏈表中。
3.3申請大小的限制
棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就肯定的常數),若是申請的空間超過棧的剩餘空間時,將提示overflow。所以,能從棧得到的空間較小。
堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是因爲系統是用鏈表來存儲的空閒內存地址的,天然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。因而可知,堆得到的空間比較靈活,也比較大。
2.4申請效率的比較:
棧由系統自動分配,速度較快。但程序員是沒法控制的。
堆是由new分配的內存,通常速度比較慢,並且容易產生內存碎片,不過用起來最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,他不是在堆,也不是在棧是直接在進程的地址空間中保留一快內存,雖然用起來最不方便。可是速度快,也最靈活。
2.5堆和棧中的存儲內容
棧: 在函數調用時,第一個進棧的是主函數中後的下一條指令(函數調用語句的下一條可執行語句)的地址,而後是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,而後是函數中的局部變量。注意靜態變量是不入棧的。
當本次函數調用結束後,局部變量先出棧,而後是參數,最後棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。
堆:通常是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。
2.6存取效率的比較
char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在運行時刻賦值的;
而bbbbbbbbbbb是在編譯時就肯定的;
可是,在之後的存取中,在棧上的數組比指針所指向的字符串(例如堆)快。
好比:
#include
void main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return;
}
對應的彙編代碼
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一種在讀取時直接就把字符串中的元素讀到寄存器cl中,而第二種則要先把指針值讀到edx中,在根據edx讀取字符,顯然慢了。
2.7小結:
堆和棧的區別能夠用以下的比喻來看出:
使用棧就象咱們去飯館裏吃飯,只管點菜(發出申請)、付錢、和吃(使用),吃飽了就走,沒必要理會切菜、洗菜等準備工做和洗碗、刷鍋等掃尾工做,他的好處是快捷,可是自由度小。
使用堆就象是本身動手作喜歡吃的菜餚,比較麻煩,可是比較符合本身的口味,並且自由度大。
4、windows進程中的內存結構
在閱讀本文以前,若是你連堆棧是什麼多不知道的話,請先閱讀文章後面的基礎知識。
接觸過編程的人都知道,高級語言都能經過變量名來訪問內存中的數據。那麼這些變量在內存中是如何存放的呢?程序又是如何使用這些變量的呢?下面就會對此進行深刻的討論。下文中的C語言代碼如沒有特別聲明,默認都使用VC編譯的release版。
首先,來了解一下 C 語言的變量是如何在內存分部的。C 語言有全局變量(Global)、本地變量(Local),靜態變量(Static)、寄存器變量(Regeister)。每種變量都有不一樣的分配方式。先來看下面這段代碼:
#include <stdio.h>
int g1=0, g2=0, g3=0;
int main()
{
static int s1=0, s2=0, s3=0;
int v1=0, v2=0, v3=0;
//打印出各個變量的內存地址
printf("0x%08x\n",&v1); //打印各本地變量的內存地址
printf("0x%08x\n",&v2);
printf("0x%08x\n\n",&v3);
printf("0x%08x\n",&g1); //打印各全局變量的內存地址
printf("0x%08x\n",&g2);
printf("0x%08x\n\n",&g3);
printf("0x%08x\n",&s1); //打印各靜態變量的內存地址
printf("0x%08x\n",&s2);
printf("0x%08x\n\n",&s3);
return 0;
}
編譯後的執行結果是[不一樣電腦會打印出不一樣的結果]:
0x0012ff78
0x0012ff7c
0x0012ff80
0x004068d0
0x004068d4
0x004068d8
0x004068dc
0x004068e0
0x004068e4
輸出的結果就是變量的內存地址。其中v1,v2,v3是本地變量,g1,g2,g3是全局變量,s1,s2,s3是靜態變量。你能夠看到這些變量在內存是連續分佈的,可是本地變量和全局變量分配的內存地址差了十萬八千里,而全局變量和靜態變量分配的內存是連續的。這是由於本地變量和全局/靜態變量是分配在不一樣類型的內存區域中的結果。對於一個進程的內存空間而言,能夠在邏輯上分紅3個部份:代碼區,靜態數據區和動態數據區。動態數據區通常就是「堆棧」。「棧(stack)」和「堆(heap)」是兩種不一樣的動態數據區,棧是一種線性結構,堆是一種鏈式結構。進程的每一個線程都有私有的「棧」,因此每一個線程雖然代碼同樣,但本地變量的數據都是互不干擾。一個堆棧能夠經過「基地址」和「棧頂」地址來描述。全局變量和靜態變量分配在靜態數據區,本地變量分配在動態數據區,即堆棧中。程序經過堆棧的基地址和偏移量來訪問本地變量。

堆棧是一個先進後出的數據結構,棧頂地址老是小於等於棧的基地址。咱們能夠先了解一下函數調用的過程,以便對堆棧在程序中的做用有更深刻的瞭解。不一樣的語言有不一樣的函數調用規定,這些因素有參數的壓入規則和堆棧的平衡。windows API的調用規則和ANSI C的函數調用規則是不同的,前者由被調函數調整堆棧,後者由調用者調整堆棧。二者經過「__stdcall」和「__cdecl」前綴區分。先看下面這段代碼:
#include <stdio.h>
void __stdcall func(int param1,int param2,int param3)
{
int var1=param1;
int var2=param2;
int var3=param3;
printf("0x%08x\n",¶m1); //打印出各個變量的內存地址
printf("0x%08x\n",¶m2);
printf("0x%08x\n\n",¶m3);
printf("0x%08x\n",&var1);
printf("0x%08x\n",&var2);
printf("0x%08x\n\n",&var3);
return;
}
int main()
{
func(1,2,3);
return 0;
}
編譯後的執行結果是[不一樣變量會打印出不一樣結果]:
0x0012ff78
0x0012ff7c
0x0012ff80
0x0012ff68
0x0012ff6c
0x0012ff70

上圖就是函數調用過程當中堆棧的樣子了。首先,三個參數以從又到左的次序壓入堆棧,先壓「param3」,再壓「param2」,最後壓入「param1」;而後壓入函數的返回地址(RET),接着跳轉到函數地址接着執行(這裏要補充一點,介紹UNIX下的緩衝溢出原理的文章中都提到在壓入RET後,繼續壓入當前EBP,而後用當前ESP代替EBP。然而,有一篇介紹windows下函數調用的文章中說,在windows下的函數調用也有這一步驟,但根據個人實際調試,並未發現這一步,這還能夠從param3和var1之間只有4字節的間隙這點看出來);第三步,將棧頂(ESP)減去一個數,爲本地變量分配內存空間,上例中是減去12字節(ESP=ESP-3*4,每一個int變量佔用4個字節);接着就初始化本地變量的內存空間。因爲「__stdcall」調用由被調函數調整堆棧,因此在函數返回前要恢復堆棧,先回收本地變量佔用的內存(ESP=ESP+3*4),而後取出返回地址,填入EIP寄存器,回收先前壓入參數佔用的內存(ESP=ESP+3*4),繼續執行調用者的代碼。參見下列彙編代碼:
;--------------func 函數的彙編代碼-------------------
:00401000 83EC0C sub esp, 0000000C //建立本地變量的內存空間
:00401003 8B442410 mov eax, dword ptr [esp+10]
:00401007 8B4C2414 mov ecx, dword ptr [esp+14]
:0040100B 8B542418 mov edx, dword ptr [esp+18]
:0040100F 89442400 mov dword ptr [esp], eax
:00401013 8D442410 lea eax, dword ptr [esp+10]
:00401017 894C2404 mov dword ptr [esp+04], ecx
……………………(省略若干代碼)
:00401075 83C43C add esp, 0000003C ;恢復堆棧,回收本地變量的內存空間
:00401078 C3 ret 000C ;函數返回,恢復參數佔用的內存空間
;若是是「__cdecl」的話,這裏是「ret」,堆棧將由調用者恢復
;-------------------函數結束-------------------------
;--------------主程序調用func函數的代碼--------------
:00401080 6A03 push 00000003 //壓入參數param3
:00401082 6A02 push 00000002 //壓入參數param2
:00401084 6A01 push 00000001 //壓入參數param1
:00401086 E875FFFFFF call 00401000 //調用func函數
;若是是「__cdecl」的話,將在這裏恢復堆棧,「add esp, 0000000C」
聰明的讀者看到這裏,差很少就明白緩衝溢出的原理了。先來看下面的代碼:
#include <stdio.h>
#include <string.h>
void __stdcall func()
{
char lpBuff[8]="\0";
strcat(lpBuff,"AAAAAAAAAAA");
return;
}
int main()
{
func();
return 0;
}
編譯後執行一下回怎麼樣?哈,「"0x00414141"指令引用的"0x00000000"內存。該內存不能爲"read"。」,「非法操做」嘍!"41"就是"A"的16進制的ASCII碼了,那明顯就是strcat這句出的問題了。"lpBuff"的大小隻有8字節,算進結尾的\0,那strcat最多隻能寫入7個"A",但程序實際寫入了11個"A"外加1個\0。再來看看上面那幅圖,多出來的4個字節正好覆蓋了RET的所在的內存空間,致使函數返回到一個錯誤的內存地址,執行了錯誤的指令。若是能精心構造這個字符串,使它分紅三部分,前一部份僅僅是填充的無心義數據以達到溢出的目的,接着是一個覆蓋RET的數據,緊接着是一段shellcode,那隻要着個RET地址能指向這段shellcode的第一個指令,那函數返回時就能執行shellcode了。可是軟件的不一樣版本和不一樣的運行環境均可能影響這段shellcode在內存中的位置,那麼要構造這個RET是十分困難的。通常都在RET和shellcode之間填充大量的NOP指令,使得exploit有更強的通用性。

windows下的動態數據除了可存放在棧中,還能夠存放在堆中。瞭解C++的朋友都知道,C++能夠使用new關鍵字來動態分配內存。來看下面的C++代碼:
#include <stdio.h>
#include <iostream.h>
#include <windows.h>
void func()
{
char *buffer=new char[128];
char bufflocal[128];
static char buffstatic[128];
printf("0x%08x\n",buffer); //打印堆中變量的內存地址
printf("0x%08x\n",bufflocal); //打印本地變量的內存地址
printf("0x%08x\n",buffstatic); //打印靜態變量的內存地址
}
void main()
{
func();
return;
}
程序執行結果爲【不一樣電腦輸出結果不一樣】:
0x004107d0
0x0012ff04
0x004068c0
能夠發現用new關鍵字分配的內存即不在棧中,也不在靜態數據區。VC編譯器是經過windows下的「堆(heap)」來實現new關鍵字的內存動態分配。在講「堆」以前,先來了解一下和「堆」有關的幾個API函數:
HeapAlloc 在堆中申請內存空間
HeapCreate 建立一個新的堆對象
HeapDestroy 銷燬一個堆對象
HeapFree 釋放申請的內存
HeapWalk 枚舉堆對象的全部內存塊
GetProcessHeap 取得進程的默認堆對象
GetProcessHeaps 取得進程全部的堆對象
LocalAlloc
GlobalAlloc
當進程初始化時,系統會自動爲進程建立一個默認堆,這個堆默認所佔內存的大小爲1M。堆對象由系統進行管理,它在內存中以鏈式結構存在。經過下面的代碼能夠經過堆動態申請內存空間:
HANDLE hHeap=GetProcessHeap();
char *buff=HeapAlloc(hHeap,0,8);
其中hHeap是堆對象的句柄,buff是指向申請的內存空間的地址。那這個hHeap到底是什麼呢?它的值有什麼意義嗎?看看下面這段代碼吧:
#pragma comment(linker,"/entry:main") //定義程序的入口
#include <windows.h>
_CRTIMP int (__cdecl *printf)(const char *, ...); //定義STL函數printf
/*---------------------------------------------------------------------------
寫到這裏,咱們順便來複習一下前面所講的知識:
(*注)printf函數是C語言的標準函數庫中函數,VC的標準函數庫由msvcrt.dll模塊實現。
由函數定義可見,printf的參數個數是可變的,函數內部沒法預先知道調用者壓入的參數個數,函數只能經過分析第一個參數字符串的格式來得到壓入參數的信息,因爲這裏參數的個數是動態的,因此必須由調用者來平衡堆棧,這裏便使用了__cdecl調用規則。BTW,Windows系統的API函數基本上是__stdcall調用形式,只有一個API例外,那就是wsprintf,它使用__cdecl調用規則,同printf函數同樣,這是因爲它的參數個數是可變的緣故。
---------------------------------------------------------------------------*/
void main()
{
HANDLE hHeap=GetProcessHeap();
char *buff=HeapAlloc(hHeap,0,0x10);
char *buff2=HeapAlloc(hHeap,0,0x10);
HMODULE hMsvcrt=LoadLibrary("msvcrt.dll");
printf=(void *)GetProcAddress(hMsvcrt,"printf");
printf("0x%08x\n",hHeap);
printf("0x%08x\n",buff);
printf("0x%08x\n\n",buff2);
}
執行結果爲【不一樣電腦輸出信息不一樣】:
0x00130000
0x00133100
0x00133118
hHeap的值怎麼和那個buff的值那麼接近呢?其實hHeap這個句柄就是指向HEAP首部的地址。在進程的用戶區存着一個叫PEB(進程環境塊)的結構,這個結構中存放着一些有關進程的重要信息,其中在PEB首地址偏移0x18處存放的ProcessHeap就是進程默認堆的地址,而偏移0x90處存放了指向進程全部堆的地址列表的指針。windows有不少API都使用進程的默認堆來存放動態數據,如windows 2000下的全部ANSI版本的函數都是在默認堆中申請內存來轉換ANSI字符串到Unicode字符串的。對一個堆的訪問是順序進行的,同一時刻只能有一個線程訪問堆中的數據,當多個線程同時有訪問要求時,只能排隊等待,這樣便形成程序執行效率降低。
最後來講說內存中的數據對齊。所位數據對齊,是指數據所在的內存地址必須是該數據長度的整數倍,DWORD數據的內存起始地址能被4除盡,WORD數據的內存起始地址能被2除盡,x86 CPU能直接訪問對齊的數據,當他試圖訪問一個未對齊的數據時,會在內部進行一系列的調整,這些調整對於程序來講是透明的,可是會下降運行速度,因此編譯器在編譯程序時會盡可能保證數據對齊。一樣一段代碼,咱們來看看用VC、Dev-C++和lcc三個不一樣編譯器編譯出來的程序的執行結果:
#include <stdio.h>
int main()
{
int a;
char b;
int c;
printf("0x%08x\n",&a);
printf("0x%08x\n",&b);
printf("0x%08x\n",&c);
return 0;
}
這是用VC編譯後的執行結果:
0x0012ff7c
0x0012ff7b
0x0012ff80
變量在內存中的順序:b(1字節)-a(4字節)-c(4字節)。
這是用Dev-C++編譯後的執行結果:
0x0022ff7c
0x0022ff7b
0x0022ff74
變量在內存中的順序:c(4字節)-中間相隔3字節-b(佔1字節)-a(4字節)。
這是用lcc編譯後的執行結果:
0x0012ff6c
0x0012ff6b
0x0012ff64
變量在內存中的順序:同上。
三個編譯器都作到了數據對齊,可是後兩個編譯器顯然沒VC「聰明」,讓一個char佔了4字節,浪費內存哦。
基礎知識:
堆棧是一種簡單的數據結構,是一種只容許在其一端進行插入或刪除的線性表。容許插入或刪除操做的一端稱爲棧頂,另外一端稱爲棧底,對堆棧的插入和刪除操做被稱爲入棧和出棧。有一組CPU指令能夠實現對進程的內存實現堆棧訪問。其中,POP指令實現出棧操做,PUSH指令實現入棧操做。CPU的ESP寄存器存放當前線程的棧頂指針,EBP寄存器中保存當前線程的棧底指針。CPU的EIP寄存器存放下一個CPU指令存放的內存地址,當CPU執行完當前的指令後,從EIP寄存器中讀取下一條指令的內存地址,而後繼續執行。
參考:《Windows下的HEAP溢出及其利用》by: isno
《windows核心編程》by: Jeffrey Richter
摘要: 討論常見的堆性能問題以及如何防範它們。(共 9 頁)
前言
您是不是動態分配的 C/C++ 對象忠實且幸運的用戶?您是否在模塊間的往返通訊中頻繁地使用了「自動化」?您的程序是否因堆分配而運行起來很慢?不只僅您遇到這樣的問題。幾乎全部項目早晚都會遇到堆問題。你們都想說,「個人代碼真正好,只是堆太慢」。那只是部分正確。更深刻理解堆及其用法、以及會發生什麼問題,是頗有用的。
一、什麼是堆?
(若是您已經知道什麼是堆,能夠跳到「什麼是常見的堆性能問題?」部分)
在程序中,使用堆來動態分配和釋放對象。在下列狀況下,調用堆操做:
事先不知道程序所需對象的數量和大小。
二、對象太大而不適合堆棧分配程序。
堆使用了在運行時分配給代碼和堆棧的內存以外的部份內存。下圖給出了堆分配程序的不一樣層。
GlobalAlloc/GlobalFree:Microsoft Win32 堆調用,這些調用直接與每一個進程的默認堆進行對話。
LocalAlloc/LocalFree:Win32 堆調用(爲了與 Microsoft Windows NT 兼容),這些調用直接與每一個進程的默認堆進行對話。
COM 的 IMalloc 分配程序(或 CoTaskMemAlloc / CoTaskMemFree):函數使用每一個進程的默認堆。自動化程序使用「組件對象模型 (COM)」的分配程序,而申請的程序使用每一個進程堆。
C/C++ 運行時 (CRT) 分配程序:提供了 malloc() 和 free() 以及 new 和 delete 操做符。如 Microsoft Visual Basic 和 Java 等語言也提供了新的操做符並使用垃圾收集來代替堆。CRT 建立本身的私有堆,駐留在 Win32 堆的頂部。
Windows NT 中,Win32 堆是 Windows NT 運行時分配程序周圍的薄層。全部 API 轉發它們的請求給 NTDLL。
Windows NT 運行時分配程序提供 Windows NT 內的核心堆分配程序。它由具備 128 個大小從 8 到 1,024 字節的空閒列表的前端分配程序組成。後端分配程序使用虛擬內存來保留和提交頁。
在圖表的底部是「虛擬內存分配程序」,操做系統使用它來保留和提交頁。全部分配程序使用虛擬內存進行數據的存取。
分配和釋放塊不就那麼簡單嗎?爲什麼花費這麼長時間?
三、堆實現的注意事項
傳統上,操做系統和運行時庫是與堆的實現共存的。在一個進程的開始,操做系統建立一個默認堆,叫作「進程堆」。若是沒有其餘堆可以使用,則塊的分配使用「進程堆」。語言運行時也能在進程內建立單獨的堆。(例如,C 運行時建立它本身的堆。)除這些專用的堆外,應用程序或許多已載入的動態連接庫 (DLL) 之一能夠建立和使用單獨的堆。Win32 提供一整套 API 來建立和使用私有堆。有關堆函數(英文)的詳盡指導,請參見 MSDN。
當應用程序或 DLL 建立私有堆時,這些堆存在於進程空間,而且在進程內是可訪問的。從給定堆分配的數據將在同一個堆上釋放。(不能從一個堆分配而在另外一個堆釋放。)
在全部虛擬內存系統中,堆駐留在操做系統的「虛擬內存管理器」的頂部。語言運行時堆也駐留在虛擬內存頂部。某些狀況下,這些堆是操做系統堆中的層,而語言運行時堆則經過大塊的分配來執行本身的內存管理。不使用操做系統堆,而使用虛擬內存函數更利於堆的分配和塊的使用。
典型的堆實現由前、後端分配程序組成。前端分配程序維持固定大小塊的空閒列表。對於一次分配調用,堆嘗試從前端列表找到一個自由塊。若是失敗,堆被迫從後端(保留和提交虛擬內存)分配一個大塊來知足請求。通用的實現有每塊分配的開銷,這將耗費執行週期,也減小了可以使用的存儲空間。
Knowledge Base 文章 Q10758,「用 calloc() 和 malloc() 管理內存」 (搜索文章編號), 包含了有關這些主題的更多背景知識。另外,有關堆實現和設計的詳細討論也可在下列著做中找到:「Dynamic Storage Allocation: A Survey and Critical Review」,做者 Paul R. Wilson、Mark S. Johnstone、Michael Neely 和 David Boles;「International Workshop on Memory Management」, 做者 Kinross, Scotland, UK, 1995 年 9 月(http://www.cs.utexas.edu/users/oops/papers.html)(英文)。
Windows NT 的實現(Windows NT 版本 4.0 和更新版本) 使用了 127 個大小從 8 到 1,024 字節的 8 字節對齊塊空閒列表和一個「大塊」列表。「大塊」列表(空閒列表[0]) 保存大於 1,024 字節的塊。空閒列表容納了用雙向鏈表連接在一塊兒的對象。默認狀況下,「進程堆」執行收集操做。(收集是將相鄰空閒塊合併成一個大塊的操做。)收集耗費了額外的週期,但減小了堆塊的內部碎片。
單一全局鎖保護堆,防止多線程式的使用。(請參見「Server Performance and Scalability Killers」中的第一個注意事項, George Reilly 所著,在 「MSDN Online Web Workshop」上(站點:http://msdn.microsoft.com/workshop/server/iis/tencom.asp(英文)。)單一全局鎖本質上是用來保護堆數據結構,防止跨多線程的隨機存取。若堆操做太頻繁,單一全局鎖會對性能有不利的影響。
四、什麼是常見的堆性能問題?
如下是您使用堆時會遇到的最多見問題:
分配操做形成的速度減慢。光分配就耗費很長時間。最可能致使運行速度減慢緣由是空閒列表沒有塊,因此運行時分配程序代碼會耗費週期尋找較大的空閒塊,或從後端分配程序分配新塊。
釋放操做形成的速度減慢。釋放操做耗費較多週期,主要是啓用了收集操做。收集期間,每一個釋放操做「查找」它的相鄰塊,取出它們並構形成較大塊,而後再把此較大塊插入空閒列表。在查找期間,內存可能會隨機碰到,從而致使高速緩存不能命中,性能下降。
堆競爭形成的速度減慢。當兩個或多個線程同時訪問數據,並且一個線程繼續進行以前必須等待另外一個線程完成時就發生競爭。競爭老是致使麻煩;這也是目前多處理器系統遇到的最大問題。當大量使用內存塊的應用程序或 DLL 以多線程方式運行(或運行於多處理器系統上)時將致使速度減慢。單一鎖定的使用—經常使用的解決方案—意味着使用堆的全部操做是序列化的。當等待鎖定時序列化會引發線程切換上下文。能夠想象交叉路口閃爍的紅燈處走走停停致使的速度減慢。
競爭一般會致使線程和進程的上下文切換。上下文切換的開銷是很大的,但開銷更大的是數據從處理器高速緩存中丟失,以及後來線程復活時的數據重建。
堆破壞形成的速度減慢。形成堆破壞的緣由是應用程序對堆塊的不正確使用。一般情形包括釋放已釋放的堆塊或使用已釋放的堆塊,以及塊的越界重寫等明顯問題。(破壞不在本文討論範圍以內。有關內存重寫和泄漏等其餘細節,請參見 Microsoft Visual C++(R) 調試文檔 。)
頻繁的分配和重分配形成的速度減慢。這是使用腳本語言時很是廣泛的現象。如字符串被反覆分配,隨重分配增加和釋放。不要這樣作,若是可能,儘可能分配大字符串和使用緩衝區。另外一種方法就是儘可能少用鏈接操做。
競爭是在分配和釋放操做中致使速度減慢的問題。理想狀況下,但願使用沒有競爭和快速分配/釋放的堆。惋惜,如今尚未這樣的通用堆,也許未來會有。
在全部的服務器系統中(如 IIS、MSProxy、DatabaseStacks、網絡服務器、 Exchange 和其餘), 堆鎖定實在是個大瓶頸。處理器數越多,競爭就越會惡化。
五、儘可能減小堆的使用
如今您明白使用堆時存在的問題了,難道您不想擁有能解決這些問題的超級魔棒嗎?我可但願有。但沒有魔法能使堆運行加快—所以不要指望在產品出貨以前的最後一星期可以大爲改觀。若是提早規劃堆策略,狀況將會大大好轉。調整使用堆的方法,減小對堆的操做是提升性能的良方。
如何減小使用堆操做?經過利用數據結構內的位置可減小堆操做的次數。請考慮下列實例:
struct ObjectA {
// objectA 的數據
}
struct ObjectB {
// objectB 的數據
}
// 同時使用 objectA 和 objectB
//
// 使用指針
//
struct ObjectB {
struct ObjectA * pObjA;
// objectB 的數據
}
//
// 使用嵌入
//
struct ObjectB {
struct ObjectA pObjA;
// objectB 的數據
}
//
// 集合 – 在另外一對象內使用 objectA 和 objectB
//
struct ObjectX {
struct ObjectA objA;
struct ObjectB objB;
}
避免使用指針關聯兩個數據結構。若是使用指針關聯兩個數據結構,前面實例中的對象 A 和 B 將被分別分配和釋放。這會增長額外開銷—咱們要避免這種作法。
把帶指針的子對象嵌入父對象。當對象中有指針時,則意味着對象中有動態元素(百分之八十)和沒有引用的新位置。嵌入增長了位置從而減小了進一步分配/釋放的需求。這將提升應用程序的性能。
合併小對象造成大對象(聚合)。聚合減小分配和釋放的塊的數量。若是有幾個開發者,各自開發設計的不一樣部分,則最終會有許多小對象須要合併。集成的挑戰就是要找到正確的聚合邊界。
內聯緩衝區可以知足百分之八十的須要(aka 80-20 規則)。個別狀況下,須要內存緩衝區來保存字符串/二進制數據,但事先不知道總字節數。估計並內聯一個大小能知足百分之八十須要的緩衝區。對剩餘的百分之二十,能夠分配一個新的緩衝區和指向這個緩衝區的指針。這樣,就減小分配和釋放調用並增長數據的位置空間,從根本上提升代碼的性能。
在塊中分配對象(塊化)。塊化是以組的方式一次分配多個對象的方法。若是對列表的項連續跟蹤,例如對一個 {名稱,值} 對的列表,有兩種選擇:選擇一是爲每個「名稱-值」對分配一個節點;選擇二是分配一個能容納(如五個)「名稱-值」對的結構。例如,通常狀況下,若是存儲四對,就可減小節點的數量,若是須要額外的空間數量,則使用附加的鏈表指針。
塊化是友好的處理器高速緩存,特別是對於 L1-高速緩存,由於它提供了增長的位置 —不用說對於塊分配,不少數據塊會在同一個虛擬頁中。
正確使用 _amblksiz。C 運行時 (CRT) 有它的自定義前端分配程序,該分配程序從後端(Win32 堆)分配大小爲 _amblksiz 的塊。將 _amblksiz 設置爲較高的值能潛在地減小對後端的調用次數。這隻對普遍使用 CRT 的程序適用。
使用上述技術將得到的好處會因對象類型、大小及工做量而有所不一樣。但總能在性能和可升縮性方面有所收穫。另外一方面,代碼會有點特殊,但若是通過深思熟慮,代碼仍是很容易管理的。
六、其餘提升性能的技術
下面是一些提升速度的技術:
使用 Windows NT5 堆
因爲幾個同事的努力和辛勤工做,1998 年初 Microsoft Windows(R) 2000 中有了幾個重大改進:
改進了堆代碼內的鎖定。堆代碼對每堆一個鎖。全局鎖保護堆數據結構,防止多線程式的使用。但不幸的是,在高通訊量的狀況下,堆仍受困於全局鎖,致使高競爭和低性能。Windows 2000 中,鎖內代碼的臨界區將競爭的可能性減到最小,從而提升了可伸縮性。
使用 「Lookaside」列表。堆數據結構對塊的全部空閒項使用了大小在 8 到 1,024 字節(以 8-字節遞增)的快速高速緩存。快速高速緩存最初保護在全局鎖內。如今,使用 lookaside 列表來訪問這些快速高速緩存空閒列表。這些列表不要求鎖定,而是使用 64 位的互鎖操做,所以提升了性能。
七、內部數據結構算法也獲得改進。
這些改進避免了對分配高速緩存的需求,但不排除其餘的優化。使用 Windows NT5 堆評估您的代碼;它對小於 1,024 字節 (1 KB) 的塊(來自前端分配程序的塊)是最佳的。GlobalAlloc() 和 LocalAlloc() 創建在同一堆上,是存取每一個進程堆的通用機制。若是但願得到高的局部性能,則使用 Heap(R) API 來存取每一個進程堆,或爲分配操做建立本身的堆。若是須要對大塊操做,也能夠直接使用 VirtualAlloc() / VirtualFree() 操做。
上述改進已在 Windows 2000 beta 2 和 Windows NT 4.0 SP4 中使用。改進後,堆鎖的競爭率顯著下降。這使全部 Win32 堆的直接用戶受益。CRT 堆創建於 Win32 堆的頂部,但它使用本身的小塊堆,於是不能從 Windows NT 改進中受益。(Visual C++ 版本 6.0 也有改進的堆分配程序。)
使用分配高速緩存
分配高速緩存容許高速緩存分配的塊,以便未來重用。這可以減小對進程堆(或全局堆)的分配/釋放調用的次數,也容許最大限度的重用曾經分配的塊。另外,分配高速緩存容許收集統計信息,以便較好地理解對象在較高層次上的使用。
典型地,自定義堆分配程序在進程堆的頂部實現。自定義堆分配程序與系統堆的行爲很類似。主要的差異是它在進程堆的頂部爲分配的對象提供高速緩存。高速緩存設計成一套固定大小(如 32 字節、64 字節、128 字節等)。這一個很好的策略,但這種自定義堆分配程序丟失與分配和釋放的對象相關的「語義信息」。
與自定義堆分配程序相反,「分配高速緩存」做爲每類分配高速緩存來實現。除可以提供自定義堆分配程序的全部好處以外,它們還可以保留大量語義信息。每一個分配高速緩存處理程序與一個目標二進制對象關聯。它可以使用一套參數進行初始化,這些參數表示併發級別、對象大小和保持在空閒列表中的元素的數量等。分配高速緩存處理程序對象維持本身的私有空閒實體池(不超過指定的閥值)並使用私有保護鎖。合在一塊兒,分配高速緩存和私有鎖減小了與主系統堆的通訊量,於是提供了增長的併發、最大限度的重用和較高的可伸縮性。
須要使用清理程序來按期檢查全部分配高速緩存處理程序的活動狀況並回收未用的資源。若是發現沒有活動,將釋放分配對象的池,從而提升性能。
能夠審覈每一個分配/釋放活動。第一級信息包括對象、分配和釋放調用的總數。經過查看它們的統計信息能夠得出各個對象之間的語義關係。利用以上介紹的許多技術之一,這種關係能夠用來減小內存分配。
分配高速緩存也起到了調試助手的做用,幫助您跟蹤沒有徹底清除的對象數量。經過查看動態堆棧返回蹤影和除沒有清除的對象以外的簽名,甚至可以找到確切的失敗的調用者。
MP 堆
MP 堆是對多處理器友好的分佈式分配的程序包,在 Win32 SDK(Windows NT 4.0 和更新版本)中能夠獲得。最初由 JVert 實現,此處堆抽象創建在 Win32 堆程序包的頂部。MP 堆建立多個 Win32 堆,並試圖將分配調用分佈到不一樣堆,以減小在全部單一鎖上的競爭。
本程序包是好的步驟 —一種改進的 MP-友好的自定義堆分配程序。可是,它不提供語義信息和缺少統計功能。一般將 MP 堆做爲 SDK 庫來使用。若是使用這個 SDK 建立可重用組件,您將大大受益。可是,若是在每一個 DLL 中創建這個 SDK 庫,將增長工做設置。
從新思考算法和數據結構
要在多處理器機器上伸縮,則算法、實現、數據結構和硬件必須動態伸縮。請看最常常分配和釋放的數據結構。試問,「我能用不一樣的數據結構完成此工做嗎?」例如,若是在應用程序初始化時加載了只讀項的列表,這個列表沒必要是線性連接的列表。若是是動態分配的數組就很是好。動態分配的數組將減小內存中的堆塊和碎片,從而加強性能。
減小須要的小對象的數量減小堆分配程序的負載。例如,咱們在服務器的關鍵處理路徑上使用五個不一樣的對象,每一個對象單獨分配和釋放。一塊兒高速緩存這些對象,把堆調用從五個減小到一個,顯著減小了堆的負載,特別當每秒鐘處理 1,000 個以上的請求時。
若是大量使用「Automation」結構,請考慮從主線代碼中刪除「Automation BSTR」,或至少避免重複的 BSTR 操做。(BSTR 鏈接致使過多的重分配和分配/釋放操做。)
摘要
對全部平臺每每都存在堆實現,所以有巨大的開銷。每一個單獨代碼都有特定的要求,但設計能採用本文討論的基本理論來減小堆之間的相互做用。
評價您的代碼中堆的使用。
改進您的代碼,以使用較少的堆調用:分析關鍵路徑和固定數據結構。
在實現自定義的包裝程序以前使用量化堆調用成本的方法。
若是對性能不滿意,請要求 OS 組改進堆。更多這類請求意味着對改進堆的更多關注。
要求 C 運行時組針對 OS 所提供的堆製做小巧的分配包裝程序。隨着 OS 堆的改進,C 運行時堆調用的成本將減少。
操做系統(Windows NT 家族)正在不斷改進堆。請隨時關注和利用這些改進。
Murali Krishnan 是 Internet Information Server (IIS) 組的首席軟件設計工程師。從 1.0 版本開始他就設計 IIS,併成功發行了 1.0 版本到 4.0 版本。Murali 組織並領導 IIS 性能組三年 (1995-1998), 從一開始就影響 IIS 性能。他擁有威斯康星州 Madison 大學的 M.S.和印度 Anna 大學的 B.S.。工做以外,他喜歡閱讀、打排球和家庭烹飪。
http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835
我在學習對象的生存方式的時候見到一種是在堆棧(stack)之中,以下
CObject object;
還有一種是在堆(heap)中 以下
CObject* pobject=new CObject();
請問
(1)這兩種方式有什麼區別?
(2)堆棧與堆有什麼區別??
---------------------------------------------------------------
1) about stack, system will allocate memory to the instance of object automatically, and to the
heap, you must allocate memory to the instance of object with new or malloc manually.
2) when function ends, system will automatically free the memory area of stack, but to the
heap, you must free the memory area manually with free or delete, else it will result in memory
leak.
3)棧內存分配運算內置於處理器的指令集中,效率很高,可是分配的內存容量有限。
4)堆上分配的內存能夠有咱們本身決定,使用很是靈活。
---------------------------------------------------------------
待定序號、拓展的程序設計
一、約瑟夫問題[Josephus 問題]
問題詳情:
聽說著名猶太曆史學家 Josephus有過如下的故事:在羅馬人佔領喬塔帕特後,39 個猶太人與Josephus及他的朋友躲到一個洞中,39個猶太人決定寧願死也不要被敵人抓到,因而決定了一個自殺方式,41我的排成一個圓圈,由第1我的開始報數,每報數到第3人該人就必須自殺,而後再由下一個從新報數,直到全部人都自殺身亡爲止。然而Josephus 和他的朋友並不想聽從。
首先看一下約瑟夫是怎麼解決的
首先從一我的開始,越過k-2我的(由於第一我的已經被越過),並殺掉第k我的。接着,再越過k-1我的,並殺掉第k我的。這個過程沿着圓圈一直進行,直到最終只剩下一我的留下,這我的就能夠繼續活着。問題是,給定了和,一開始要站在什麼地方纔能避免被處決?Josephus要他的朋友先僞裝聽從,他將朋友與本身安排在第16個與第31個位置,因而逃過了這場死亡遊戲。
二、把圖中的數組轉置

三、一元多項式的相加
提示:
第一種方法:一元多項式的表示問:對於任意一元多項式:
Pn(x)=P0+P1X1+P2X2+P3X3+P4X4+............PnXn
能夠抽象爲一個由「係數-指數」對構成的線性表,且線性表中各元素的指數項遞增:即
P=((P0,0),(P1,1),(P2,2),(P3,3),(P4,4).........(Pn,n))
第二種方法:用一個單鏈表表示上述線性表,結點結構爲:
typedef struct node{
float coef;/*係數域*/
int exp;/*指數域*/
struct node *next;/*指針域*/
}PloyNode;
結點圖示
