[Lập trình C] Hàm - function

Posted on January 8th, 2021

Trong các bài viết trước, bạn đã được học những kiến thức cơ bản về kiểu dữ liệu trong lập trình C. Khi thực hành, bạn thường viết toàn bộ code của chương trình trong một hàm main() duy nhất. Cách viết code này sẽ khiến bạn khó theo dõi logic chương trình. Và khi gặp lỗi, bạn cũng khó hơn để phát hiện và sửa lỗi.

Để giải quyết vấn đề này, bạn có thể chia chương trình ra thành nhiều hàm con. Mỗi hàm con sẽ đảm nhiệm một chức năng, giải quyết một bài toán nhỏ trong toàn bộ bài toán lớn.

Vì vậy, bài viết này giới thiệu với bạn cách để sử dụng hàm trong ngôn ngữ lập trình C.

Kiến thức cơ bản về hàm trong lập trình C

Hàm là gì?

Hàm là một chương trình con được sinh ra để giải quyết một bài toán cụ thể nào đó.

Trong hàm sẽ có một hoặc nhiều câu lệnh.

Một số lợi ích của hàm là:

  • Hàm giúp cho chương trình dễ viết và dễ hiểu hơn.
  • Hàm giúp cho việc debug chương trình dễ hơn dựa vào việc module hóa.
  • Hàm giúp chương trình dễ bảo trì hơn. Khi có yêu cầu mới, bạn chỉ cần sửa những hàm liên quan. Các hàm khác có thể bỏ qua.

Cấu trúc cơ bản của hàm

Cú pháp chung

Cú pháp chung của một hàm trong ngôn ngữ lập trình C là:

kiểu_trả_về tên_hàm(danh_sách_tham_số) {
	nội dung của hàm
}

Trong đó:

  • kiểu_trả_về: xác định kiểu dữ liệu của giá trị trả về bởi hàm.
  • tên_hàm: định danh hàm; tên hàm nên được đặt với nội dung miêu tả hoạt động của hàm
  • danh_sách_tham_số: là các tham số được truyền vào hàm để sử dụng bên trong nội dung hàm

Ví dụ cách khai báo hàm tính bình phương của một số:

#include <stdio.h>

int tinhBinhPhuong(int x) {
	int ketQua;
	ketQua = x*x;
	return ketQua;
}

int main() {
	int i;
	for(i = 1; i <= 10; i++) {
		printf("Binh phuong cua %d la: %d\n", i, tinhBinhPhuong(i));
	}
	return 0;
}

Hàm trên có:

  • Kiểu giá trị trả về là: kiểu int
  • Tên hàm là: tinhBinhPhuong
  • Tham số truyền vào là: số nguyên x

Tham số truyền vào hàm

Trong ví dụ trên, giá trị của tham số được truyền vào hàm là i.

Khi đó, i tương đương với x bên trong hàm tinhBinhPhuong

Trả về của hàm

Cũng trong ví dụ trên, câu lệnh để trả về giá trị của hàm là return ketQua;.

Biểu thức theo sau return chính là giá trị được trả về bởi hàm.

Và một khi hàm được trả về giá trị (gặp return) thì hàm sẽ dừng ngay lập tức.

Kiểu dữ liệu trả về của hàm

Kiểu dữ liệu trả về của hàm có thể là một những kiểu dữ liệu cơ bản như: int, char, float, double, void, array...

Trong đó, nếu hàm trả về kiểu dữ liệu void, thì hiểu là hàm đó không trả về giá trị nào cả.

Ngoài ra, với hàm trả về dữ liệu kiểu void hoặc kiểu int thì bạn có thể bỏ qua, không cần ghi kiểu dữ liệu trả về.

Tuy nhiên, để tránh hiểu lầm, bạn nên ghi kiểu giá trị trả về cho hàm với bất kỳ kiểu dữ liệu nào.

Cách khai báo hàm và gọi hàm

Khai báo hàm

Trước khi sử dụng hàm, bạn cần phải khai báo hàm.

Sau đó, bạn có thể định nghĩa hàm luôn tại vị trí khai báo hoặc ở chỗ khác (phía dưới phần code gọi hàm)

Ví dụ trên minh học cách khai báo và định nghĩa làm luôn, trước khi gọi hàm.

Hoặc bạn có thể khai báo hàm trước rồi định nghĩa hàm sau như ví dụ:

#include <stdio.h>

// Khai bao ham
int tinhBinhPhuong(int x); 

int main() {
	int i;
	for(i = 1; i <= 10; i++) {
		printf("Binh phuong cua %d la: %d\n", i, tinhBinhPhuong(i));
	}
	return 0;
}

// Dinh nghia ham
int tinhBinhPhuong(int x) {
	int ketQua;
	ketQua = x*x;
	return ketQua;
}

Nguyên mẫu hàm

Trong cách khai báo hàm trước rồi định nghĩa hàm sau như trên, bạn có thể khai báo hàm dạng nguyên mẫu như sau:

#include <stdio.h>

// Khai bao ham dang nguyen mau
int tinhBinhPhuong(int); 

int main() {
	int i;
	for(i = 1; i <= 10; i++) {
		printf("Binh phuong cua %d la: %d\n", i, tinhBinhPhuong(i));
	}
	return 0;
}

// Dinh nghia ham
int tinhBinhPhuong(int x) {
	int ketQua;
	ketQua = x*x;
	return ketQua;
}

Nghĩa là bạn chỉ ghi kiểu dữ liệu của tham số truyền vào mà không cần viết tên của tham số đó.

Gọi hàm

Sau khi khai báo hàm, bạn có thể gọi nó với tham số truyền vào tương ứng và kết thúc bằng dấu chấm phẩy (;)

Chú ý:

  • Chỉ có một giá trị duy nhất được trả về bởi hàm
  • Một chương trình có thể có nhiều hàm
  • Một hàm có thể được gọi bên trong một hàm khác. Hay nói cách khác là trong một hàm có thể gọi nhiều hàm con.

Phạm vi của biến trong hàm

Biến địa phương (local) được khai báo trong hàm và chỉ tồn tại ở trong hàm. Sau khi hàm kết thúc, biến local sẽ bị giải phóng.

Tham số hàm được khai báo ở phần định nghĩa hàm, nó hoạt động tương tự như biến local.

Biến toàn cục là biến được khai báo bên ngoài tất cả các hàm. Nó có thời gian sống là toàn bộ chương trình.

Chú ý: Nếu một biến được khai báo dạng static thì nó có thời gian sống là toàn bộ chương trình.

Quy tắc phạm vi hàm

Quy tắc phạm vi hàm đề cập tới việc là một đoạn code có thể được biết hoặc được truy cập từ một đoạn code khác hay không.

Chú ý:

  • Một đoạn code ở trong hàm là private với hàm đó.
  • Hai hàm có 2 phạm vi khác nhau.
  • Một hàm không thể được định nghĩa bên trong một hàm khác.

Các cách truyền tham số vào hàm

Truyền tham số kiểu giá trị

Trong ngôn ngữ lập trình C, mặc định khi truyền tham số vào hàm thì đó là truyền theo kiểu tham trị (truyền tham số theo giá trị).

Khi đó, chỉ giá trị của tham số là được truyền vào hàm. Mọi thay đổi ở trong hàm chỉ có tác dụng với biến tạm dùng để lưu những giá trị đó mà không làm thay đổi biến gốc truyền vào.

Ví dụ 1:

int tinhTong(int x, int y) {
	return x + y;
}

Hàm tính tổng trên truyền tham số theo kiểu giá trị. Hàm này lấy giá trị của 2 biến truyền vào là x và y. Rồi trả về giá trị số nguyên int là tổng của 2 số đầu vào.

Ví dụ 2 (sai):

int x = 6; 
int y = 10;

void hoanDoi(int x, int y) {
	int temp = x;
	x = y;
	y = temp;
}

Bạn chú ý, hàm hoán đổi trên sẽ cho kết quả sai. Bởi vì tham số x, y được truyền vào theo kiểu giá trị. Bạn có thể hình dung là bên trong hàm sẽ tạo ra biến x_ là copy của x và biến y_ là copy của y.

Rồi khi hoán đổi, bạn chỉ thực hiện hoán đổi trên những biến tạm x_ và y_. Biến truyền vào là x, y không bị thay đổi giá trị.

Truyền tham số kiểu con trỏ

Bạn có thể truyền tham số vào hàm theo kiểu con trỏ để đảm bảo việc thay đổi ở trong hàm cũng sẽ có tác dụng với biến ở ngoài hàm.

Ví dụ:

#include <stdio.h>

// Khai bao ham truyen tham so kieu con tro
void hoanDoi(int *x, int *y) {
	int temp = *x;
	*x = *y;
	*y = temp;
}

int main() {
	int x = 9;
	int y = 6;
	
	// Hoan doi gia tri cua 2 bien
	hoanDoi(&x, &y);
	
	printf("x=%d\n", x);
	printf("y=%d\n", y);
	
	return 0;
}

Đối với cách truyền tham số kiểu con trỏ, khi gọi hàm bạn cần truyền vào một con trỏ. Đối với biến x, y thông thường như trên, bạn cần phải dùng toán tử (&) để lấy ra địa chỉ của biến x và y để truyền vào hàm.

Truyền tham số kiểu mảng vào hàm

Khi bạn truyền tham số kiểu mảng vào hàm thì mặc định là sẽ truyền vào địa chỉ hàm.

Ví dụ truyền tham số kiểu mảng 1 chiều vào hàm:

#include <stdio.h>

// Khai bao ham kieu truyen vao con tro
void change1(int *a) {
	if (a != NULL) {
		a[0] = 1;
	}
}

// Khai bao ham kieu truyen vao mang
// KHONG truyen vao kich thuoc mang
void change2(int a[]) {
	a[0] = 2;
}

// Khai bao ham kieu truyen vao mang
// CO truyen vao kich thuoc mang
void change3(int a[2]) {
	a[0] = 3;
}

int main() {
	int a[2] = {3, 4};
	
	change1(a);
	printf("kieu 1 - a[0]=%d\n", a[0]);
	
	change2(a);
	printf("kieu 2 - a[0]=%d\n", a[0]);
	
	change3(a);
	printf("kieu 3 - a[0]=%d\n", a[0]);
	
	return 0;
}

Ví dụ truyền tham số kiểu mảng 2 chiều vào hàm:

#include <stdio.h>

// Khai bao ham kieu truyen vao mang
// KHONG truyen vao kich thuoc mang
void change1(int a[][3]) {
	a[0][0] = 2;
}

// Khai bao ham kieu truyen vao mang
// CO truyen vao kich thuoc mang
void change2(int a[2][3]) {
	a[0][0] = 3;
}

int main() {
	int a[2][3] = {3, 4, 5, 6, 7, 8};
	
	change1(a);
	printf("kieu 1 - a[0]=%d\n", a[0][0]);
	
	change2(a);
	printf("kieu 2 - a[0]=%d\n", a[0][0]);
	
	return 0;
}

Ví dụ 3, truyền thêm tham số là kích thước mảng để thao tác với các phần tử mảng:

#include <stdio.h>

// Khai bao ham print mang
void printArray(int numRow, int numCol, int a[2][3]) {
	int i, j;
	for(i = 0; i < numRow; i++) {
		for(j = 0; j < numCol; j++) {
			printf("%d ", a[i][j]);
		}
		printf("\n");
	}
}

int main() {
	int a[2][3] = {3, 4, 5, 6, 7, 8};
	printArray(2, 3, a);
	return 0;
}

Bài tập thực hành về hàm trong lập trình C

Bài 1

Nhập vào 2 số nguyên a, b:

  • Viết hàm để nhập vào giá trị của 2 số
  • Viết hàm trả về trung bình cộng của 2 số (gợi ý: trung bình cộng có thể là số thực)
  • Viết hàm để hoán đổi giá trị của 2 số
#include <stdio.h>

void nhap(int* x, int* y) {
	scanf("%d %d", x, y);
}

float tbc(int x, int y) {
	float ketQua;
	ketQua = (float)(x + y) / 2;
	return ketQua;
}

void hoanDoi(int* x, int* y) {
	int temp = *x;
	*x = *y;
	*y = temp;
}

int main() {
	int a, b;
	
	// Nhap vao 2 so
	printf("1. Nhap vao 2 so: ");
	nhap(&a, &b);
	printf("++ Hai so da nhap la: a=%d b=%d\n\n", a, b);
	
	// Tinh trung binh cong 2 so
	printf("2. Tinh trung binh cong 2 so: \n");
	float t = tbc(a, b);
	printf("++ Trung binh cong la: %g\n\n", t);
	
	// Hoan doi 2 so
	printf("3. Hoan doi 2 so: \n");
	hoanDoi(&a, &b);
	printf("++ Gia tri cua 2 so sau hoan doi: a=%d b=%d\n\n", a, b);
	
	return 0;
}

Bài 2

Nhập vào 3 số nguyên a, b, c:

  • Viết hàm để nhập vào giá trị của 3 số
  • Viết hàm trả về giá trị nhỏ nhất của 3 số
  • Viết hàm trả về giá trị lớn nhất của 3 số
#include <stdio.h>

void nhap(int* x, int* y, int* z) {
	scanf("%d %d %d", x, y, z);
}

int minOf(int x, int y, int z) {
	int ketQua = x;
	if (y < ketQua) {
		ketQua = y;
	}
	if (z < ketQua) {
		ketQua = z;
	}
	return ketQua;
}

int maxOf(int x, int y, int z) {
	int ketQua = x;
	if (y > ketQua) {
		ketQua = y;
	}
	if (z > ketQua) {
		ketQua = z;
	}
	return ketQua;
}

int main() {
	int a, b, c;
	
	// Nhap vao 3 so
	printf("1. Nhap vao 3 so la: ");
	nhap(&a, &b, &c);
	printf("++ 3 so da nhap la: a=%d b=%d c=%d\n\n", a, b, c);
	
	// Gia tri nho nhat cua 3 so
	printf("2. Gia tri nho nhat cua 3 so: \n");
	int min = minOf(a, b, c);
	printf("++ gia tri nho nhat la: %d\n\n", min);
	
	// Gia tri lon nhat cua 3 so
	printf("3. Gia tri lon nhat cua 3 so: \n");
	int max = maxOf(a, b, c);
	printf("++ gia tri lon nhat la: %d\n\n", max);	
		
	return 0;
}

Bài 3

Nhập vào mảng số nguyên N phần tử (0 < N <= 50):

  • Viết hàm nhập vào giá trị của N
  • Viết hàm nhập vào N phần tử của mảng
  • Viết hàm in ra các phần tử mảng từ đầu đến cuối
  • Viết hàm in ra các phần tử mảng từ cuối lên đầu
#include <stdio.h>

void nhap(int *n) {
	scanf("%d", n);
}

void nhapMang(int n, int arr[50]) {
	int i;
	for(i = 0; i < n; i++) {
		scanf("%d", &arr[i]);
	}
}

void printMangTuDauDenCuoi(int n, int arr[50]) {
	int i;
	for (i = 0; i < n; i++) {
		printf("%d ", arr[i]);
	}
}

void printMangTuCuoiLenDau(int n, int arr[50]) {
	int i;
	for (i = n - 1; i >= 0; i--) {
		printf("%d ", arr[i]);
	}
}

int main() {
	int N;
	int a[50];

	// Nhap vao gia tri N
	printf("1. Nhap vao N la: ");
	nhap(&N);
	printf("++ gia tri N da nhap: %d\n\n", N);

	// Nhap vao gia tri cua N phan tu trong mang
	printf("2. Nhap vao cac phan tu cua mang: \n");
	printf("++ ");
	nhapMang(N, a);
	printf("\n");

	// In ra cac phan tu mang tu dau den cuoi
	printf("3. Cac phan tu mang tu dau den cuoi la: \n");
	printf("++ ");
	printMangTuDauDenCuoi(N, a);
	printf("\n\n");

	// In ra cac phan tu mang tu cuoi len dau
	printf("4. Cac phan tu mang tu cuoi len dau la: \n");
	printf("++ ");
	printMangTuCuoiLenDau(N, a);
	printf("\n\n");

	return 0;
}

Bài 4

Nhập vào mảng số nguyên N phần tử (0 < N <= 50):

  • Viết hàm nhập vào giá trị của N
  • Viết hàm nhập vào N phần tử của mảng
  • Viết hàm tính tổng các phần tử lẻ của mảng
  • Viết hàm sắp xếp mảng theo thứ tự tăng dần
#include <stdio.h>

void nhap(int *n) {
	scanf("%d", n);
}

void nhapMang(int n, int arr[50]) {
	int i;
	for(i = 0; i < n; i++) {
		scanf("%d", &arr[i]);
	}
}

void printMang(int n, int arr[50]) {
	int i;
	for(i = 0; i < n; i++) {
		printf("%d ", arr[i]);
	}
}

long long int tinhTongLe(int n, int arr[50]) {
	long long int ketQua = 0;
	int i;
	for (i = 0; i < n; i++) {
		if (arr[i] % 2 != 0) {
			ketQua += arr[i];
		}
	}
	return ketQua;
}

void hoanDoi(int *x, int *y) {
	int temp = *x;
	*x = *y;
	*y = temp;
}

void sapXepTangDan(int n, int arr[50]) {
	int i, j;
	for(i = 0; i < n - 1; i++) {
		for(j = i + 1; j < n; j++) {
			if (arr[i] > arr[j]) {
				hoanDoi(&arr[i], &arr[j]);
			}
		}
	}
}

int main() {
	int N;
	int a[50];
	
	// Nhap N
	printf("1. Nhap N: ");
	nhap(&N);
	printf("++ gia tri da nhap la: %d\n\n", N);
	
	// Nhap N phan tu cua mang
	printf("2. Nhap cac phan tu mang: \n");
	printf("++ ");
	nhapMang(N, a);
	
	printf("++ Mang da nhap la: ");
	printMang(N, a);
	printf("\n\n");
	
	// Tinh tong cac phan tu le
	printf("3. Tinh tong cac phan tu le: \n");
	long long tongLe = tinhTongLe(N, a);
	printf("++ tong la: %lld\n\n", tongLe);
	
	// Sap xep mang theo thu tu tang dan
	printf("4. Sap xep theo thu tu tang dan: \n");
	sapXepTangDan(N, a);
	
	printf("++ Mang da sap xep la: ");
	printMang(N, a);
	printf("\n\n");

	return 0;
}

Bài 5

Nhập vào chuỗi kí tự tối đa 20 kí tự:

  • Viết hàm nhập vào chuỗi kí tự (có kiểm tra điều kiện để đảm bảo nhập vào không quá 20 kí tự).
  • Viết duy nhất một hàm tìm vị trí của các kí tự 'a' xuất hiện trong chuỗi kí tự và trả về mảng của các vị trí đó, cùng với số lượng các vị trí tìm được.

Ví dụ: chuỗi nhập vào là "abcabc" thì phải trả về được 2 thông tin:

  • Mảng các vị trí của chữ cái a: int pos[] = {0, 3}
  • Số lượng các vị trí tìm được: int soLuong = 2

Nếu không tìm thấy thì trả về mảng rỗng và số lượng vị trí tìm được là 0.

#include <stdio.h>
#include <string.h>

const int MAX_CH = 20;

void nhapChuoi(char str[MAX_CH + 1]) {
	int i = 0;
	while(1) {
		if (i >= MAX_CH) {
			str[i] = '\0';
			break;
		}
		str[i] = getchar();
		if (str[i] == '\n') {
			str[i] = '\0';
			break;
		}
		i++;
	}
}

int timVaDemSoLuongKiTu(char ch, char str[MAX_CH + 1], int vt[MAX_CH]) {
	int count = 0, i;
	for(i = 0; i < MAX_CH; i++) {
		if (str[i] == 'a') {
			vt[count] = i;
			count += 1;
		}
	}
	return count;
} 

int main() {
	char s[MAX_CH + 1];
	
	// Nhap chuoi ki tu
	printf("1. Nhap vao chuoi la: ");
	nhapChuoi(s);
	printf("++ chuoi da nhap la: %s\n\n", s);
	
	// Tim va dem vi tri cac ki tu 'a'
	printf("2. Tim va dem vi tri ki tu 'a' la: \n");
	int i;
	int viTri[MAX_CH];
	int soLuong = timVaDemSoLuongKiTu('a', s, viTri);
	printf("++ so luong ki tu 'a' la: %d\n", soLuong);
	printf("++ vi tri ki tu 'a' la: ");
	for (i = 0; i < soLuong; i++) {
		printf("%d ", viTri[i]);
	}

	return 0;
}