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

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á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>intmain(){char* charReference =malloc(1);printf("n Memory address: %p", charReference);// - - - - - - - - -*charReference ='c';printf("n Stored character: %c",*charReference);// - - - - - - - - -free(charReference);return0;}
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>intmain(){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);return0;}
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>intmain(){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);return0;}
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};constchangeValue=(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 – …

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ