Python SimpleHTTPServerWithUpload 上傳檔案功能

本篇 ShengYu 介紹 SimpleHTTPServerWithUpload 上傳檔案功能,之前在這篇介紹怎麼使用 Python SimpleHTTPServer 快速建立簡單網頁伺服器 http web sever,這篇是網頁伺服器加上檔案上傳的功能。

https://gist.github.com/UniIsland/3346170
但是這個版本在上傳中文檔案時結果頁面會顯示亂碼,但資料夾中上傳的中文檔案名稱是正常。原因是因為它網頁編碼問題,所以我改了一版加上 utf-8 在結果頁面就可以正常顯示中文,並加上一些空白檔案名稱的錯誤處理,以及上傳時如果檔名是空的話會顯示提示視窗。
缺點是不支援多檔上傳,這樣就不方便,有點可惜。
以下是我修改的版本,

python-SimpleHTTPServerWithUpload.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
#!/usr/bin/env python

"""Simple HTTP Server With Upload.
This module builds on BaseHTTPServer by implementing the standard GET
and HEAD requests in a fairly straightforward manner.
"""


__version__ = "0.3"
__all__ = ["SimpleHTTPRequestHandler"]
__author__ = "ShengYu"
__home_page__ = "https://shengyu7697.github.io/"

import os
import posixpath
import BaseHTTPServer
import urllib
import cgi
import shutil
import mimetypes
import re
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO


class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):

"""Simple HTTP request handler with GET/HEAD/POST commands.
This serves files from the current directory and any of its
subdirectories. The MIME type for files is determined by
calling the .guess_type() method. And can reveive file uploaded
by client.
The GET/HEAD/POST requests are identical except that the HEAD
request omits the actual contents of the file.
"""

server_version = "SimpleHTTPWithUpload/" + __version__

def do_GET(self):
"""Serve a GET request."""
f = self.send_head()
if f:
self.copyfile(f, self.wfile)
f.close()

def do_HEAD(self):
"""Serve a HEAD request."""
f = self.send_head()
if f:
f.close()

def do_POST(self):
"""Serve a POST request."""
r, info = self.deal_post_data()
print(str(r) + ' ' + info + ' by: %s' % (self.client_address,))
f = StringIO()
f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n')
f.write('<html lang="en">\n')
f.write('<head>\n')
f.write('<meta charset="utf-8">\n')
f.write('</head>\n')
f.write('<title>Upload Result Page</title>\n')
f.write('<body>\n')
f.write('<h2>Upload Result Page</h2>\n')
f.write('<hr>\n')
if r:
f.write('<strong>Success:</strong> ')
else:
f.write('<strong>Failed:</strong> ')
f.write(info)
f.write('<br><a href="%s">back</a>' % self.headers['referer'])
f.write('<hr><small>Powerd By: ShengYu, check new version at ')
f.write('<a href="https://shengyu7697.github.io/SimpleHTTPServerWithUpload">')
f.write('here</a>.</small></body>\n</html>\n')
length = f.tell()
f.seek(0)
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.send_header('Content-Length', str(length))
self.end_headers()
if f:
self.copyfile(f, self.wfile)
f.close()

def deal_post_data(self):
boundary = self.headers.plisttext.split('=')[1]
remainbytes = int(self.headers['content-length'])
line = self.rfile.readline()
remainbytes -= len(line)
if not boundary in line:
return (False, 'Content NOT begin with boundary')
line = self.rfile.readline()
remainbytes -= len(line)
fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line)
if not fn:
return (False, 'Can\'t find out file name...')
path = self.translate_path(self.path)
fn = os.path.join(path, fn[0])
line = self.rfile.readline()
remainbytes -= len(line)
line = self.rfile.readline()
remainbytes -= len(line)
try:
out = open(fn, 'wb')
except IOError:
return (False, 'Can\'t create file to write, do you have permission to write?')

preline = self.rfile.readline()
remainbytes -= len(preline)
while remainbytes > 0:
line = self.rfile.readline()
remainbytes -= len(line)
if boundary in line:
preline = preline[0:-1]
if preline.endswith('\r'):
preline = preline[0:-1]
out.write(preline)
out.close()
return (True, 'File "%s" upload success!' % fn)
else:
out.write(preline)
preline = line
return (False, 'Unexpect Ends of data.')

def send_head(self):
"""Common code for GET and HEAD commands.
This sends the response code and MIME headers.
Return value is either a file object (which has to be copied
to the outputfile by the caller unless the command was HEAD,
and must be closed by the caller under all circumstances), or
None, in which case the caller has nothing further to do.
"""
path = self.translate_path(self.path)
f = None
if os.path.isdir(path):
if not self.path.endswith('/'):
# redirect browser - doing basically what apache does
self.send_response(301)
self.send_header('Location', self.path + '/')
self.end_headers()
return None
for index in 'index.html', 'index.htm':
index = os.path.join(path, index)
if os.path.exists(index):
path = index
break
else:
return self.list_directory(path)
ctype = self.guess_type(path)
try:
# Always read in binary mode. Opening files in text mode may cause
# newline translations, making the actual size of the content
# transmitted *less* than the content-length!
f = open(path, 'rb')
except IOError:
self.send_error(404, 'File not found')
return None
self.send_response(200)
self.send_header('Content-type', ctype)
fs = os.fstat(f.fileno())
self.send_header('Content-Length', str(fs[6]))
self.send_header('Last-Modified', self.date_time_string(fs.st_mtime))
self.end_headers()
return f

def list_directory(self, path):
"""Helper to produce a directory listing (absent index.html).
Return value is either a file object, or None (indicating an
error). In either case, the headers are sent, making the
interface the same as for send_head().
"""
try:
list = os.listdir(path)
except os.error:
self.send_error(404, 'No permission to list directory')
return None
list.sort(key=lambda a: a.lower())
f = StringIO()
displaypath = cgi.escape(urllib.unquote(self.path))
f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
f.write('<html lang="en">\n')
f.write('<head>\n')
f.write('<meta charset="utf-8">\n')
f.write('</head>\n')
f.write('<title>Directory listing for %s</title>\n' % displaypath)
f.write('<body>\n')
f.write('<h2>Directory listing for %s</h2>\n' % displaypath)
f.write('<hr>\n')
f.write('<form ENCTYPE="multipart/form-data" method="post" name="my_form">\n')
f.write('<input type="file" id="file" name="file"/>\n')
f.write('<input type="button" value="upload" onclick="my_submit()"/>\n')
f.write('</form>\n')
f.write('<hr>\n<ul>\n')
for name in list:
fullname = os.path.join(path, name)
displayname = linkname = name
# Append / for directories or @ for symbolic links
if os.path.isdir(fullname):
displayname = name + "/"
linkname = name + "/"
if os.path.islink(fullname):
displayname = name + "@"
# Note: a link to a directory displays with @ and links with /
f.write('<li><a href="%s">%s</a>\n'
% (urllib.quote(linkname), cgi.escape(displayname)))
f.write('</ul>\n<hr>\n')
f.write('<script>\n'
'function validation() {\n'
' var file = document.getElementById("file").value;\n'
' if (file === "") {\n'
' alert("file is empty!");\n'
' return false;\n'
' }\n'
' return true;\n'
'}\n'
'\n'
'function my_submit() {\n'
' if (validation()) {\n'
' var x = document.getElementsByName("my_form");\n'
' x[0].submit();\n'
' }\n'
'}\n'
'</script>\n')
f.write('</body>\n</html>\n')
length = f.tell()
f.seek(0)
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.send_header('Content-Length', str(length))
self.end_headers()
return f

def translate_path(self, path):
"""Translate a /-separated PATH to the local filename syntax.
Components that mean special things to the local file system
(e.g. drive or directory names) are ignored. (XXX They should
probably be diagnosed.)
"""
# abandon query parameters
path = path.split('?',1)[0]
path = path.split('#',1)[0]
path = posixpath.normpath(urllib.unquote(path))
words = path.split('/')
words = filter(None, words)
path = os.getcwd()
for word in words:
drive, word = os.path.splitdrive(word)
head, word = os.path.split(word)
if word in (os.curdir, os.pardir): continue
path = os.path.join(path, word)
return path

def copyfile(self, source, outputfile):
"""Copy all data between two file objects.
The SOURCE argument is a file object open for reading
(or anything with a read() method) and the DESTINATION
argument is a file object open for writing (or
anything with a write() method).
The only reason for overriding this would be to change
the block size or perhaps to replace newlines by CRLF
-- note however that this the default server uses this
to copy binary data as well.
"""
shutil.copyfileobj(source, outputfile)

def guess_type(self, path):
"""Guess the type of a file.
Argument is a PATH (a filename).
Return value is a string of the form type/subtype,
usable for a MIME Content-type header.
The default implementation looks the file's extension
up in the table self.extensions_map, using application/octet-stream
as a default; however it would be permissible (if
slow) to look inside the data to make a better guess.
"""

base, ext = posixpath.splitext(path)
if ext in self.extensions_map:
return self.extensions_map[ext]
ext = ext.lower()
if ext in self.extensions_map:
return self.extensions_map[ext]
else:
return self.extensions_map['']

if not mimetypes.inited:
mimetypes.init() # try to read system mime.types
extensions_map = mimetypes.types_map.copy()
extensions_map.update({
'': 'application/octet-stream', # Default
'.py': 'text/plain',
'.c': 'text/plain',
'.h': 'text/plain',
})


def test(HandlerClass = SimpleHTTPRequestHandler,
ServerClass = BaseHTTPServer.HTTPServer):
BaseHTTPServer.test(HandlerClass, ServerClass)

if __name__ == '__main__':
test()

加上 utf-8 的方法也很簡單,就是在 head 之前加上 <meta charset="utf-8">,如下所示,

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<title>Hello</title>
<body>
</body>
</html>

uploadserver

另外一個版本是使用 uploadserver(pypi 專頁連結),這個版本的優點是一次可以上傳多個檔案,跟支援 curl 指令上傳。

安裝的指令如下,

1
python3 -m pip install --user uploadserver

要使用的話就這樣輸入指令,

1
python3 -m uploadserver

然後要上傳的話要到 http://ip:8000/upload 頁面再選擇檔案上傳。

以上就是 Python SimpleHTTPServerWithUpload 上傳檔案功能的介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
Python 新手入門教學懶人包
Python http web server 快速建立網頁伺服器

adb 查詢 Android SDK 版本號 (adb shell getprop)

本篇介紹如何使用 adb 指令查詢 Android SDK 版本號,想要知道 Android 裝置是使用哪個 Android SDK 版本的話,有很多種方法,這篇要介紹用 adb 指令來達成 Android SDK 版本查詢。

adb 指令查詢 Android SDK 版本號使用方式如下,

1
adb shell getprop ro.build.version.sdk

以上就是 adb 查詢 Android SDK 版本號 (adb shell getprop)的介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關推薦文章
Android adb 基本用法教學
adb 查詢 Android 版本號 (adb shell getprop)
Android adb logcat 基本用法教學
Android adb forward 通訊埠轉發用法教學

adb 查詢 Android 版本號 (adb shell getprop)

本篇介紹如何使用 adb 指令查詢 Android 版本號,想要知道 Android 裝置是使用哪個版本的話,有很多種方法,這篇要介紹用 adb 指令來達成 Android 版本查詢。

adb 指令查詢 Android 版本號使用方式如下,

1
adb shell getprop ro.build.version.release

範例結果如下,結果顯示是 Android 10,也就是開發代號 Android Q,

1
2
$ adb shell getprop ro.build.version.release
10

以上就是 adb 查詢 Android 版本號 (adb shell getprop)的介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關推薦文章
Android adb 基本用法教學
adb 查詢 Android SDK 版本號 (adb shell getprop)
Android adb logcat 基本用法教學
Android adb forward 通訊埠轉發用法教學

Visual Studio Code 關閉存檔自動排版的2種方式

本篇介紹 Visual Studio Code 關閉存檔自動排版 format,關閉 Visual Studio Code 存檔自動排版有兩種方式,

VS Code 關閉存檔自動排版的方法分為這兩種方式,

  • 從 UI 介面關閉存檔自動排版
  • 從 setting.json 設定檔關閉存檔自動排版

從 UI 介面關閉存檔自動排版

Ctrl + , 快捷鍵打開設定 setting,macOS 快捷鍵是 Cmd + ,

選擇 Text Editor > Formatting,將 Format On Save 取消勾選,這樣就不會存檔自動排版了,中文介面的話是選擇 文字編輯器 > 格式化

從 setting.json 設定檔關閉存檔自動排版

從 setting.json 設定檔關閉存檔自動排版的方法就是在 setting.json 加上 "editor.formatOnSave": false 就是存檔時不進行自動排版,可以另外使用 Alt + Shift + F 手動執行自動排版,

setting.json
1
2
3
{
"editor.formatOnSave": false
}

以上就是 Visual Studio Code 關閉存檔自動排版的2種方式介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其他參考
visual studio code - VSCode - Disable ALL Auto Formatting on Save - Stack Overflow
https://stackoverflow.com/questions/61827206/vscode-disable-all-auto-formatting-on-save
https://linuxpip.org/vscode-format-on-save/

Linux tar 用法與範例

本篇 ShengYu 介紹 Linux tar 的用法與範例。tar 是 Linux 系統中常用的打包工具,其全名為 tape archive,意思為「磁帶存檔」,最早設計用來備份檔案到磁帶中。隨著時間演進,tar 已成為一個功能強大的打包工具,用於將多個檔案和目錄合併成單一的壓縮檔案,便於傳輸或儲存。本文將介紹 tar 的基本用法以及一些實用的進階操作。

以下 Linux tar 的內容將分為這幾部份,

  • Linux tar 打包
  • Linux tar 打包壓縮
  • Linux tar 打包指定目的路徑
  • Linux tar 打包檔案直接傳輸到遠端伺服器

Linux tar 打包

最常見的用途就是將檔案或目錄打包成 .tar 檔案,並透過不同的壓縮演算法壓縮檔案,以下介紹打包與解壓用法,
tar 打包(無壓縮),單純打包無壓縮,優點是速度快,相對的容量大小幾乎跟原本的一樣,例如:將 aaa 資料夾打包成 aaa.tar

1
tar cvf aaa.tar aaa/

tar 解壓縮,例如:將 aaa.tar 解壓縮

1
tar xvf aaa.tar

Linux tar 打包壓縮

tar 打包(gz壓縮),單純打包無壓縮,優點是壓縮後檔案較原本的小,相對的壓縮與解壓縮需花費時間,例如:將 aaa 資料夾打包成 aaa.tar.gz

1
tar zcvf aaa.tar.gz aaa/

tar 解壓縮,例如:將 aaa.tar.gz 解壓縮

1
tar xvf aaa.tar.gz

Linux tar 打包指定目的路徑

使用 tar 命令打包時,可以使用 -C 選項指定目的路徑。

1
tar cvf /目標路徑/檔案名稱.tar -C /來源路徑 檔案或目錄名稱

Linux tar 打包檔案直接傳輸到遠端伺服器

如果你需要將打包的檔案直接傳輸到遠端伺服器,可以將 tar 的輸出透過 SSH 管道傳送。

1
tar -cvf - /來源路徑 | ssh 使用者名稱@遠端伺服器 "cat > /遠端目標路徑/檔案名稱.tar"

這裡的 - 代表將打包的結果輸出到標準輸出,而不是儲存到本地檔案中。
ssh 使用者名稱@遠端伺服器 “cat > /遠端目標路徑/檔案名稱.tar”:這部分透過 SSH 登入到遠端伺服器,並將接收到的標準輸出內容儲存到指定的目標路徑。

在本機目錄也可以使用類似的方法,將 tar 的輸出透過管道 (pipe) 直接傳遞給 cat 或其他命令來儲存檔案。

1
tar -cvf - /來源路徑 | cat > /目標路徑/檔案名稱.tar

與以下指令相同的效果:

1
tar -cvf /目標路徑/檔案名稱.tar /來源路徑

以上就是 Linux tar 用法與範例的介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
Linux 常用指令教學懶人包
Linux cut 字串處理用法與範例
Linux sed 字串取代用法與範例
Linux grep/ack/ag 搜尋字串用法與範例
Linux du 查詢硬碟剩餘空間/資料夾容量用法與範例
Linux wget 下載檔案用法與範例

Linux curl 指令下載檔案範例

本篇 ShengYu 介紹 curl 指令用法,如何使用 curl 指令來下載檔案。

curl 下載檔案,要加 -O 的選項,否則會將檔案的內容輸出到螢幕(標準輸出)上,

1
curl -O <URL>

curl 下載多個檔案

1
curl -O <URL1> -O <URL2> -O <URL3>

如果 URL 中檔案帶有順序的數字的話,可以使用正則表達式來批次下載多個檔案,例如:批次下載 filename1.zip ~ filename10.zip

1
curl -O http://xxx/filename[1-10].zip

如果檔案序列號有補 0 的話,例如:filename01.zip, filename02.zip ~ filename10.zip,那麼可以這樣下,

1
curl -O http://xxx/filename[01-10].zip

curl 限制最大傳輸速度,常見單位有 m 跟 k,例如:限速度 1MB 就使用 --limit-rate 1m,限速度 100KB 就使用 --limit-rate 100k

1
2
curl --limit-rate 1m -O http://xxx/filename.zip
curl --limit-rate 100k -O http://xxx/filename.zip

以上就是 Linux curl 指令下載檔案範例的介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
Linux 常用指令教學懶人包
Linux wget 下載檔案用法與範例
Linux sed 字串取代用法與範例
Linux find 尋找檔案/尋找資料夾用法與範例
Linux grep/ack/ag 搜尋字串用法與範例
Linux tee 同時螢幕標準輸出和輸出到檔案用法與範例
Linux xargs 參數列表轉換用法與範例
Linux tail 持續監看檔案輸出用法與範例
Linux du 查詢硬碟剩餘空間/資料夾容量用法與範例

Android adb logcat 設定改變 buffer size 加大緩衝區大小

本篇 ShengYu 介紹 Android 的 adb logcat 如何設定 buffer size 改變緩衝區大小,以便取得更多的 log。

查看目前 logcat 的 buffer size,指令如下,

1
adb logcat -g

以我手上的 Android 裝置為例的話,結果如下,

1
2
3
4
$ adb logcat -g
main: ring buffer is 256Kb (200Kb consumed), max entry is 5120b, max payload is 4076b
system: ring buffer is 256Kb (33Kb consumed), max entry is 5120b, max payload is 4076b
crash: ring buffer is 256Kb (0b consumed), max entry is 5120b, max payload is 4076b

logcat 中的 buffer size 類型共有三種,分別為 system main crash。

設定 logcat 中所有類型的 buffer size 為 16M,指令如下,

1
adb logcat -G 16M

以我手上的 Android 裝置為例的話,結果如下,

1
2
$ adb logcat -G 16M
$ adb logcat -g

如果單位是要用 kilobytes 的話可以這樣下,

1
adb logcat -G 512K

設定 logcat 中 system 類型的 buffer size 為 8M,指令如下,

1
adb logcat -b system -G 8M

以上就是 Android adb logcat 設定改變 buffer size 加大緩衝區大小教學介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其他參考
How to change size of logcat buffer in Android? - Stack Overflow
https://stackoverflow.com/questions/12397780/how-to-change-size-of-logcat-buffer-in-android
usb debugging - How do I increase the size of logcat buffers? - Android Enthusiasts Stack Exchange
https://android.stackexchange.com/questions/17572/how-do-i-increase-the-size-of-logcat-buffers
How to Increase the Logcat Buffer Size
https://pspdfkit.com/blog/2018/how-to-increase-the-logcat-buffer-size/

相關主題
Android adb 基本用法教學
Android adb 同步時間/設定時間
Android adb shell input 事件用法
Android adb forward 通訊埠轉發用法教學
Anddroid VS Code 遠端除錯教學

C/C++ struct 用法與範例

本篇 ShengYu 介紹 C/C++ struct 結構用法與範例,struct 可以將不同資料類型集合在一起,通常將相關的變數類型放在同一個 struct 裡,也方便參數傳遞。

以下 C/C++ struct 結構的用法介紹將分為這幾部份,

  • C/C++ struct 基本用法
  • C/C++ struct 計算大小
  • struct 在 C/C++ 中的使用差異
  • C/C++ typedef struct 取別名

那我們開始吧!

C/C++ struct 基本用法

以下為 C/C++ struct 基本用法,以 student 有 id、age、name 屬性為例,struct 初始化有兩種寫法,
一種是先宣告 struct 後初始化,另一種是宣告 struct 時同時初始化的寫法,

cpp-struct.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// g++ cpp-struct.cpp -o a.out
#include <stdio.h>
#include <string.h>

struct student {
int id;
int age;
char name[32];
};

int main() {
struct student s1;
s1.id = 10;
s1.age = 18;
strcpy(s1.name, "Tom");
printf("id: %d\n", s1.id);
printf("age: %d\n", s1.age);
printf("name: %s\n", s1.name);

struct student s2 = {11, 20, "Jerry"};
printf("id: %d\n", s2.id);
printf("age: %d\n", s2.age);
printf("name: %s\n", s2.name);

return 0;
}

輸出如下,

1
2
3
4
5
6
id: 10
age: 18
name: Tom
id: 11
age: 20
name: Jerry

定義 struct 順便宣告變數(s3)的寫法,

1
2
3
4
5
struct student {
int id;
int age;
char name[32];
} s3;

定義 struct 同時宣告多個變數(s3與s4)的話,用逗號連接即可,

1
2
3
4
5
struct student {
int id;
int age;
char name[32];
} s3, s4;

C/C++ struct 計算大小

計算該 struct 的大小,

cpp-struct2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// g++ cpp-struct2.cpp -o a.out
#include <stdio.h>
#include <string.h>

struct student {
int id;
int age;
char name[32];
};

int main() {
printf("int size: %d\n", sizeof(int));

struct student s1;
s1.id = 10;
printf("struct size: %d\n", sizeof(s1));

struct student s2;
s1.id = 11;
strcpy(s2.name, "shengyu");
printf("struct size: %d\n", sizeof(s2));

return 0;
}

輸出如下,

1
2
3
int size: 4
struct size: 40
struct size: 40

struct 在 C/C++ 中的使用差異

struct 在 C 中宣告時要寫 struct,在 C++ 宣告時就不需加上 struct,

1
2
struct student s1; // C/C++
student s2; // C++

C/C++ typedef struct 取別名

C/C++ 經常使用 typedef 把某個 struct 取一個別名,以下示範用 typedef 將 struct student 取一個 student_t 別名,之後宣告時就可以使用新的 student_t 別名,就可以省去加上 struct,藉此達到簡化宣告語法,

cpp-struct3.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// g++ cpp-struct3.cpp -o a.out
#include <stdio.h>
#include <string.h>

typedef struct student {
int id;
int age;
char name[32];
} student_t;

int main() {
student_t s1;
s1.id = 123;
s1.age = 20;
printf("id: %d\n", s1.id);
printf("age: %d\n", s1.age);

return 0;
}

輸出如下,

1
2
id: 123
age: 20

另外還可以把 struct 的定義跟 typedef 分開寫,像這樣寫,

cpp-struct4.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// g++ cpp-struct4.cpp -o a.out
#include <stdio.h>
#include <string.h>

struct student {
int id;
int age;
char name[32];
};

typedef struct student student_t;

int main() {
student_t s1;
s1.id = 123;
s1.age = 20;
printf("id: %d\n", s1.id);
printf("age: %d\n", s1.age);

return 0;
}

以上就是 C/C++ struct 的用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
如果你想學習 C/C++ 相關技術,可以參考看看下面的文章,
C/C++ 新手入門教學懶人包
C/C++ enum 用法與範例
C/C++ union 用法與範例

C/C++ union 用法與範例

本篇 ShengYu 介紹 C/C++ union 用法與範例,union 是一種將不同資料類型 data type 儲存在同一塊記憶體空間的型別,所有在 union 裡宣告的變數會共享同一塊記憶體空間,union 佔用的記憶體空間會以 union 內宣告的變數類型最大 size 的變數空間。

  • C/C++ union 基本用法
  • C/C++ union 初始化
  • C/C++ typedef union 取別名
  • C/C++ 匿名 union

C/C++ union 基本用法

C/C++ union 只會選擇一種變數類型儲存,且會以變數類型最大 size 的變數空間作為 union 佔用的記憶體空間,如下範例,
student union 中的 id 與 name 一次只能選擇使用一種存取,不能同時使用兩者,因為 id 與 name 佔的是同一塊記憶體空間,

cpp-union.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// g++ cpp-union.cpp -o a.out
#include <stdio.h>
#include <string.h>

union student {
int id;
char name[32];
};

int main() {
printf("int size: %d\n", sizeof(int));

union student s1;
s1.id = 1;
printf("union size: %d\n", sizeof(s1));

union student s2;
strcpy(s2.name, "shengyu");
printf("union size: %d\n", sizeof(s2));

return 0;
}

輸出如下,

1
2
3
int size: 4
union size: 32
union size: 32

C/C++ union 初始化

這邊介紹 C/C++ union 初始化的幾種寫法,如下範例所示,

cpp-union2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// g++ cpp-union2.cpp -o a.out
#include <stdio.h>

union data {
char c;
int i;
float f;
};

int main() {
union data d1;
d1.c = 'a';
printf("%c\n", d1.c);

union data d2 = { 'b' };
printf("%c\n", d2.c);

union data d3 = d2;
printf("%c\n", d3.c);

return 0;
}

輸出如下,

1
2
3
a
b
b

C/C++ typedef union 取別名

每次宣告變數時都必須加上 union,如果想要省略每次宣告變數前的 union,可以利用 typedef 取別名的方式,如下範例,使用 typedef 將 union student 取一個 Student 新別名後,之後宣告時就可以使用新的 Student 別名,就可以省去加上 union,藉此達到簡化宣告語法,

cpp-union3.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// g++ cpp-union3.cpp -o a.out
#include <stdio.h>
#include <string.h>

typedef union student {
int id;
char name[16];
} Student;

int main() {
Student s1;
s1.id = 1;
printf("union size: %d\n", sizeof(s1));

Student s2;
strcpy(s2.name, "shengyu");
printf("union size: %d\n", sizeof(s2));

return 0;
}

另外還有另外一種寫法,可以把 union 的定義跟 typedef 分開寫,typedef 最後面記得要加上分號,

1
2
3
4
5
6
union student {
int id;
char name[32];
};

typedef union student Student;

C/C++ 匿名 union

這邊介紹一下 C/C++ 匿名 union(anonymous union),先從一般的 union 開始說明,一般的 union 可以放在 struct 裡,如下範例所示,

cpp-union4.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// g++ cpp-union4.cpp -o a.out
#include <stdio.h>

union data {
int i;
char c;
};

struct student {
int id;
union data d;
};

int main() {
struct student s;
s.id = 1;
s.d.c = 2;
printf("%d\n", s.d.c);

return 0;
}

也可以這樣寫,直接將 data union 寫在 student struct 裡,

cpp-union5.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// g++ cpp-union5.cpp -o a.out
#include <stdio.h>

struct student {
int id;
union {
int i;
char c;
} data;
};

int main() {
struct student s;
s.id = 1;
s.data.c = 2;
printf("%d\n", s.data.c);

return 0;
}

如果拿掉 data union 的名稱的話,這樣寫的話就是匿名 union(anonymous union),如下範例所示,差別在存取匿名 union 可以少寫一層,直接使用 union 中元素的名稱來存取該元素,少了在前面 union 的名稱,少一點程式碼,

cpp-union6.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// g++ cpp-union6.cpp -o a.out
#include <stdio.h>

struct student {
int id;
union {
int i;
char c;
};
};

int main() {
struct student s;
s.id = 1;
s.c = 2;
printf("%d\n", s.c);

return 0;
}

以上就是 C/C++ union 的用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
如果你想學習 C/C++ 相關技術,可以參考看看下面的文章,
C/C++ 新手入門教學懶人包
C/C++ enum 用法與範例
C/C++ struct 用法與範例

C/C++ enum 用法與範例

本篇 ShengYu 介紹 C/C++ enum 列舉用法與範例,

以下 C/C++ enum 列舉的用法介紹將分為這幾部份,

  • C/C++ enum 基本用法
  • C/C++ enum 指定值
  • C/C++ typedef enum 取別名
  • C/C++ enum class 限制 scope C++11
  • C/C++ enum 繼承

那我們開始吧!

C/C++ enum 基本用法

C/C++ enum 列舉預設從 0 開始,後續的列舉項目如果後面不指定值的話預設會累加 1,基本範例如下,

cpp-enum.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// g++ cpp-enum.cpp -o a.out
#include <stdio.h>

enum fruit {
apple,
banana,
lemon,
mango,
orange
};

int main() {
printf("%d\n", apple);
printf("%d\n", banana);
printf("%d\n", lemon);
printf("%d\n", ::mango);
printf("%d\n", ::orange);

printf("===\n");
fruit f = apple;
printf("%d\n", f);

return 0;
}

輸出如下,可以看到 apple 的值為 0,因為 enum 列舉從 0 開始,
banana 的值為 1,因為 enum 列舉會自動加 1,後續依此類推,
列舉項目是全域的,所以加上 :: 也是一樣的意思,如上例中的 ::mango
另外 enum 列舉可以用來宣告變數,然後存放這個列舉項目,如上例中的 f 變數,

1
2
3
4
5
6
7
0
1
2
3
4
===
0

C/C++ enum 指定值

C/C++ enum 列舉如果要指定值的話,可以這樣寫,

cpp-enum2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// g++ cpp-enum2.cpp -o a.out
#include <stdio.h>

enum fruit {
apple = 1,
banana = 2,
lemon,
mango,
orange
};

int main() {
printf("%d\n", apple);
printf("%d\n", banana);
printf("%d\n", lemon);
printf("%d\n", mango);
printf("%d\n", orange);

printf("===\n");
fruit f = lemon;
printf("%d\n", f);

return 0;
}

輸出如下,指定 apple 為 1,指定 banana 為 2,所以 lemon 會繼續累加變成 3,後續依此類推,

1
2
3
4
5
6
7
1
2
3
4
5
===
3

那如果 banana 與 lemon 同樣指定為 2 後續會是怎樣呢?

1
2
3
4
5
6
7
enum fruit {
apple = 1,
banana = 2,
lemon = 2,
mango,
orange
};

結果會是這樣,mango 還是會繼續累加變成 3,後續依此類推,

1
2
3
4
5
1
2
2
3
4

C/C++ typedef enum 取別名

C/C++ 用 typedef 可以將某 enum 取一個新別名,以下示範用 typedef 將 fruit 這個 enum 取一個 FRUIT 新別名,之後宣告時就可以使用新的 FRUIT 別名,就可以省去加上 enum,藉此達到簡化宣告語法,

cpp-enum3.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// g++ cpp-enum3.cpp -o a.out
#include <stdio.h>

typedef enum fruit {
apple,
banana,
lemon,
mango,
orange
} FRUIT;

int main() {
fruit f1 = apple;
FRUIT f2 = apple;
FRUIT f3 = banana;
printf("%d\n", apple);
printf("%d\n", f1);
printf("%d\n", f2);
printf("%d\n", f3);

return 0;
}

輸出如下,

1
2
3
4
0
0
0
1

另外還有另外一種寫法,可以把 union 的定義跟 typedef 分開寫,typedef 最後面記得要加上分號,

1
2
3
4
5
6
7
8
9
enum fruit {
apple,
banana,
lemon,
mango,
orange
};

typedef enum fruit FRUIT;

C/C++ enum class 限制 scope C++11

範圍列舉 scoped enumerations C++11 才支援的,一般的 enum 是全域範圍,也就是不能有另外一個 enum 裡面也有相同名稱的列舉項目,
以下面例子 fruit2 為例,enum 裡面也跟 fruit 同樣有同名稱的 apple, banana, lemon,就會出現編譯錯誤,
為了解決這個問題,C++11 就有了 scoped enumerations 可以限制這個列舉的範圍,

cpp-enum4.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// g++ cpp-enum4.cpp -o a.out -std=c++11
#include <stdio.h>
#include <stdint.h>

enum fruit {
apple,
banana,
lemon
};

//enum fruit2 { // Compiler Error, redefinition
// apple,
// banana,
// lemon
//};

enum class fruit3 {
apple,
banana,
lemon
};

enum class fruit4 {
apple,
banana,
lemon
};

int main() {
printf("%d\n", apple);
printf("%d\n", ::banana);
printf("%d\n", ::lemon);

printf("===\n");
printf("%d\n", fruit3::apple);
printf("%d\n", fruit3::banana);
printf("%d\n", fruit3::lemon);

printf("===\n");
printf("%d\n", fruit4::apple);
printf("%d\n", fruit4::banana);
printf("%d\n", fruit4::lemon);

printf("===\n");
fruit4 f = fruit4::apple;
//fruit4 f2 = apple; // Compiler Error
printf("%d\n", f);

return 0;
}

輸出如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
0
1
2
===
0
1
2
===
0
1
2
===
0

C/C++ enum 繼承

C/C++ enum 繼承後會影響該 enum 列舉的大小,enum 列舉繼承指定類型的大小主要是可以節省記憶體,如下範例所示,

cpp-enum5.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// g++ cpp-enum5.cpp -o a.out -std=c++11
#include <stdio.h>
#include <stdint.h>

enum color {
red,
yellow,
blue
};

enum fruit2 : int8_t {
apple,
banana,
lemon
};

//enum fruit3 : int8_t { // Compiler Error, redefinition
// apple,
// banana,
// lemon
//};

enum class fruit4 : int8_t {
apple,
banana,
lemon
};

enum class fruit5 : int32_t {
apple,
banana,
lemon
};

int main() {
printf("%d\n", sizeof(color::red));
printf("%d\n", sizeof(fruit2::apple));
printf("%d\n", sizeof(fruit4::apple));
printf("%d\n", sizeof(fruit5::apple));

color c = red;
printf("%d\n", sizeof(color::red));
fruit2 f2;
printf("%d\n", sizeof(f2));
fruit4 f4;
printf("%d\n", sizeof(f4));
fruit5 f5;
printf("%d\n", sizeof(f5));

return 0;
}

輸出如下,

1
2
3
4
5
6
7
8
4
1
1
4
4
1
1
4

以上就是 C/C++ enum 的用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
如果你想學習 C/C++ 相關技術,可以參考看看下面的文章,
C/C++ 新手入門教學懶人包
C/C++ union 用法與範例
C/C++ struct 用法與範例