- vừa được xem lúc

[Imperative Programming + C] Bài 3 - Pointer & Scanner

0 0 20

Người đăng: Semi Art

Theo Viblo Asia

Cách mà chúng ta đã tạo ra và sử dụng các biến ở bài trước là một trong số hai giao diện lập trình mà C cung cấp. Nó khá gần gũi và thân thiện với góc nhìn của một newbie đã có nền tảng lập trình căn bản từ JavaScript. Thôi thì chúng ta cứ tạm gọi đó là giao diện lập trình bậc cao đi. 😄

Giao diện lập trình bậc thấp

Bên cạnh đó thì C còn cung cấp một giao diện lập trình khác, mà khi sử dụng giao diện này thì chúng ta sẽ có thể hiểu được rõ hơn những phép màu của các ngôn ngữ lập trình bậc cao như JavaScript. Và sau khi đã hoàn toàn hiểu rõ thì đó sẽ là những phép màu thực sự chứ chắc chắn sẽ không phải là những điểm tiềm năng tạo ra các vấn đề nữa. 😄

#include <stdio.h>
#include <stdlib.h> int main() { char* charReference = malloc(1); printf("\n Memory address: %p", charReference); // - - - - - - - - - *charReference = 'c'; printf("\n Stored character: %c", *charReference); // - - - - - - - - - free(charReference); return 0;
}
gcc main.c -o main
main Memory address: 0000000000c013d0 Stored character: c

Ở đây chúng ta có một đoạn code ví dụ tạo ra một cái hộp charReference định kiểu là char* - có thêm ký hiệu * so với mấy cái hint ở bài trước. Những chiếc hộp được gắn hint kèm hậu tố * như thế này được gọi là con trỏ - hay pointer - lưu địa chỉ của một vùng nào đó trong bộ nhớ máy tính.

Như ví dụ ở trên thì địa chỉ của vùng bộ nhớ mà charReference đang trỏ đến là 0000000000c013d0. Cái địa chỉ này được chương trình malloc (memory allocate) trả về khi chúng ta truyền vào một giá trị số học mô tả độ rộng của vùng bộ nhớ cần sử dụng.

char* charReference = malloc(1);

Thao tác này là một dạng khai báo với hệ điều hành để được cấp phát quyền sử dụng một phần nhỏ bộ nhớ đệm cho chương trình mà chúng ta đang viết. Có như vậy thì các chương trình khác trong cùng máy tính mới không ghi đè dữ liệu vận hành lên các vùng bộ nhớ mà chương trình này đang sử dụng, và ngược lại. 😄 Lúc này khi nhắc tới cái tên charReference ở đoạn printf sau đó, C sẽ hiểu là chúng ta đang muốn xem cái địa chỉ dài dòng kia, chứ không phải là một ký tự chữ cái.

printf("\n Memory address: %p", charReference);

Và để lưu một ký tự vào vùng bộ nhớ đó thì thao tác gán giá trị cần được viết như thế này:

*charReference = 'c';

Ký hiệu * được gắn trước charReference sẽ biểu thị là chúng ta đang muốn thực hiện thao tác lưu trữ một giá trị tại địa chỉ đó. Và lần thứ hai khi sử dụng printf, thao tác truy xuất *charReference biểu thị là chúng ta muốn lấy ra giá trị đã được lưu trữ chứ không phải là cái địa chỉ dài dòng kia.

printf("\n Stored character: %c", *charReference);

Cuối cùng là khi đã sử dụng xong vùng bộ nhớ cho mục đích lưu trữ tạm thời khi chương trình vận hành, chúng ta cần trả lại cho hệ điều hành để các phần mềm khác có thể đăng ký được sử dụng.

free(charReference);

Độ rộng kiểu dữ liệu

Rồi... như vậy là giao diện lập trình bậc cao thực ra đã tự động hóa thao tác đăng ký quyền sử dụng một vùng bộ nhớ khi chúng ta khai báo một biến có định kiểu; Còn việc chúng ta có lưu một giá trị nào đó vào vùng bộ nhớ đã đăng ký hay không thì còn tùy vào logic của code sử dụng sau đó.

Thắc mắc mới của mình lúc này là cái giá trị độ rộng truyền vào chương trình con malloc. Nhiều hơn 1 có được không? Nếu có thì bao nhiêu sẽ là đủ? Thế rồi những câu hỏi này dẫn mình tới khái niệm về độ rộng của các kiểu dữ liệu.

Cụ thể thì là dữ liệu được lưu trữ trong máy tính hay các thiết bị thông minh mà chúng ta đang sử dụng được biểu thị bằng các ô nhớ nhỏ li ti gọi là bit. Mỗi một ô nhớ này có thể lưu một trong hai giá trị là 0 hoặc 1. Và để biểu thị một giá trị số học hay một ký tự đơn thì người ta cần tạo ra một bộ quy ước để chuyển đổi từ một dãy bit sang kiểu dữ liệu thông thường mà chúng ta hay sử dụng.

Và để biểu thị một chữ cái, hay một giá trị thuộc kiểu char thì người ta đã quy ước là cần 8 ô nhớ liên tiếp cạnh nhau. Vì vậy kiểu char được xem là có độ rộng 8 bit hoặc 1 byte.

Các kiểu dữ liệu khác nhau được tạo ra bởi nhu cầu biểu thị dữ liệu lưu trữ khác nhau và hiển nhiên sẽ có độ rộng khác nhau. Tuy nhiên thì chúng ta sẽ không cần phải tìm và ghi nhớ độ rộng của từng kiểu dữ liệu để làm việc ở cấp độ này. C có cung cấp một chương trình con nho nhỏ có tên là sizeof() để trả về độ rộng của kiểu dữ liệu mà chúng ta muốn sử dụng.

#include <stdio.h>
#include <stdlib.h> int main() { char* character = malloc(sizeof(char)); int* integer = malloc(sizeof(int)); float* floating = malloc(sizeof(float)); // - - - - - - - - - *character = 'c'; *integer = 1001; *floating = 10.01; // - - - - - - - - - printf("\n A character: %c", *character); printf("\n An integer: %i", *integer); printf("\n A floating-point number: %f", *floating); // - - - - - - - - - free(character); free(integer); free(floating); return 0;
}
gcc main.c -o main
main A character: c An integer: 1001 A floating-point number: 10.010000

Chương trình đọc scanf

Sau khi đã hiểu được thêm một chút về việc định kiểu dữ liệu trong C thì thứ mà mình rất quan tâm là các thao tác I/O để nhập/xuất dữ liệu cơ bản. Cụ thể là thao tác in dữ liệu ra màn hình console thì cũng đã khá quen thuộc rồi. Bây giờ chỉ cần thêm thao tác nhận dữ liệu vào từ thao tác người dùng qua console nữa là có thể bắt đầu nghĩ tới ý tưởng viết một chương trình nhập/xuất dữ liệu đơn giản.

Vì vậy nên mình lại thêm một lượt Google nữa và tìm được một chương trình con scanf được thiết kế để hoạt động bổ trợ cho cái printf đã biết. Cái sub-program này sẽ nhận vào một địa chỉ lưu trữ của một vùng bộ nhớ và lưu nội dung quét được từ console vào vùng bộ nhớ đó. Vừa hay, mới học xong về pointer là có cái xài luôn. 😄

#include <stdio.h>
#include <stdlib.h> int main() { char* character = malloc(sizeof(char)); int* integer = malloc(sizeof(int)); float* floating = malloc(sizeof(float)); // - - - - - - - - - printf("Input a character: "); scanf("%c", character); printf("Input an integer: "); scanf("%i", integer); printf("Input a floating-point number: "); scanf("%f", floating); // - - - - - - - - - printf("\n A character: %c", *character); printf("\n An integer: %i", *integer); printf("\n A floating-point number: %f", *floating); // - - - - - - - - - free(character); free(integer); free(floating); return 0;
}
gcc main.c -o main
main Input a character: c
Input an integer: 1001
Input a floating-point number: 10.01 A character: c An integer: 1001 A floating-point number: 10.010000

Value & Reference in JavaScript

Như chúng ta đã thấy thì khi gọi một chương trình con sub-program trong C, chúng ta có thể chọn truyền giá trị đã lưu trữ, hoặc truyền địa chỉ của vùng bộ nhớ lưu trữ giá trị đó.

Tuy nhiên thì đối với các ngôn ngữ lập trình bậc cao như JavaScript thì mọi thứ lại được tự động hóa theo một nguyên tắc nhất định mà chúng ta không thể can thiệp được. Cụ thể là khi chúng ta tạo ra một chiếc hộp lưu trữ bằng var, let, hay const; Sau đó truyền chiếc hộp này vào một lời gọi sub-program thì thứ mà sub-program nhận được sẽ luôn luôn là địa chỉ tham chiếu tới vùng bộ nhớ mà chiếc hộp đó đang trỏ tới.

const just = { value: 1001 }; const changeValue = (reference) => { reference.value = 10;
} changeValue(just);
console.log(just);
// { value: 10 }

Trong ví dụ trên thì khi chúng ta thực hiện thao tác changeValue(just), JavaScript đã không tạo ra một object mới là bản sao của object ban đầu; Mà thay vào đó thì thứ được truyền vào là địa chỉ tham chiếu của object đó, và vì vậy nên code bên trong chương trình con changeValue đã có thể thay đổi nội dung của object ban đầu.

Trong trường hợp khác, nếu just là một giá trị đơn nguyên primitive chứ không phải là một object thì cách mà JavaScript xử lý vẫn hoàn toàn tương tự. Tuy nhiên các giá trị primitive được JavaScript biểu thị cố định ở cấp độ lưu trữ bậc thấp và không cung cấp công cụ nào để thay đổi các bit bộ nhớ ở đây. Do đó nên về cơ bản là một sub-program trong JavaScript sẽ không bao giờ có thể thay đổi nội dung của một biến đang lưu trữ một giá trị primitive.

Hmm... C... kỳ diệu thật. 😄

(Sắp đăng tải) [Imperative Programming + C] Bài 4 - ...

Bình luận

Bài viết tương tự

- vừa được xem lúc

Closure trong Javascript - Phần 2: Định nghĩa và cách dùng

Các bạn có thể đọc qua phần 1 ở đây. Để mọi người không quên, mình xin tóm tắt gọn lại khái niệm lexical environment:.

0 0 66

- vừa được xem lúc

Var vs let vs const? Các cách khai báo biến và hằng trong Javascript

Dạo này mình tập tành học Javascript, thấy có 2 cách khai báo biến khác nhau nên đã tìm tòi sự khác biệt. Nay xin đăng lên đây để mọi người đọc xong hy vọng phân biệt được giữa let và var, và sau đó là khai báo hằng bằng const.

0 0 47

- vừa được xem lúc

VueJS: Tính năng Mixins

Chào mọi người, hôm nay mình sẽ viết về Mixins và 1 số vấn đề trong sử dụng Mixins hay ho mà mình gặp trong dự án thực. Trích dẫn từ trang chủ của VueJS:.

0 0 41

- vừa được xem lúc

Asset Pipeline là cái chi chi?

Asset Pipeline. Asset pipeline là cái chi chi. . Giải thích:.

0 0 69

- vừa được xem lúc

Tạo data table web app lấy dữ liệu từ Google Sheets sử dụng Apps Script

Google Sheets là công cụ tuyệt vời để lưu trữ bảng tính trực tuyến, bạn có thể truy cập bảng tính bất kỳ lúc nào ở bất kỳ đâu và luôn sẵn sàng để chia sẻ với người khác. Bài này gồm 2 phần.

0 0 280

- vừa được xem lúc

Học Deep Learning trên Coursera miễn phí

Bạn muốn bắt đầu với Deep Learning nhưng không biết bắt đầu từ đâu? Bạn muốn có một công việc ở mức fresher về Deep Learning? Bạn muốn khoe bạn bè về kiến thức Deep Learning của mình. Bắt đầu từ đâu.

0 0 50