30/09/2018, 23:28

Gặp lỗi khi thêm dòng vào ma trận trong C/C++

Hi mọi người.

Mình có làm 1 bài tập sau nhưng không hiểu vì sao gặp lỗi !

Đề bài: Thực hiện thao tác thêm 1 dòng vào ma trận cho trước bằng con trỏ cấp 2.

Source code: http://codepad.org/9dqCpkTH

void NhapMaTran(int **a, int dong, int cot)
{
	for (int i = 0; i < dong * cot; i++)
	{
		printf("
Nhap a[%d][%d] = ", i / cot, i % cot);
		scanf("%d", *(a + i / cot) + i % cot);
	}
}
void XuatMaTran(int **a, int dong, int cot)
{
	for (int i = 0; i < dong * cot; i++)
	{
		printf("%5d", *(*(a + i / cot) + i % cot));
		if ((i + 1) % cot == 0)
			printf("
");
	}
}
void HoanVi(int &a, int &b)
{
	int Temp = a;
	a = b;
	b = Temp;
}
void SwapLine(int **a, int cot, int kdong1, int kdong2)
{
	for (int i = 0; i < cot; i++)
	{
		HoanVi(a[kdong1][i], a[kdong2][i]);
	}
}
void ThemDong(int **a, int &dong, int cot, int kdong, int *b)
{
	a[dong] = (int *)malloc(cot * sizeof(int));
	for (int i = dong; i > kdong; i--)
	{
		SwapLine(a, cot, i - 1, i);
	}
	dong++;
	int idx = 0;
	for (int i = 0; i < cot; i++)
		a[kdong][i] = b[i];
}
int main()
{
	int dong, cot;
	do
	{
		printf("
Nhap so luong dong: ");
		scanf("%d", &dong);
		printf("
Nhap so luong cot: ");
		scanf("%d", &cot);
		if (dong < 1 || cot < 1)
			printf("
So luong dong hoac cot khong hop le
");
	} while (dong < 1 || cot < 1);
	int **a = (int **)malloc(dong * sizeof(int));
	for (int i = 0; i < dong; i++)
		a[i] = (int *)malloc(cot * sizeof(int));
 
	NhapMaTran(a, dong, cot);
	XuatMaTran(a, dong, cot);
 
	int kdong;
	do
	{
		printf("
Nhap chi so dong can them: ");
		scanf("%d", &kdong);
		if (kdong < 0 || kdong > dong)
			printf("
Chi so dong can them khong hop le
");
	} while (kdong < 0 || kdong > dong);
	int *b = (int *)malloc(cot * sizeof(int));
	for (int i = 0; i < cot; i++)
	{
		printf("
Nhap a[%d][%d] = ", kdong, i);
		scanf("%d", &b[i]);
	}
	printf("
Ma tran sau khi them dong %d:
", kdong);
	ThemDong(a, dong, cot, kdong, b);
	XuatMaTran(a, dong, cot);
 
	for (int i = 0; i < dong; i++)
		free(a[i]);
	free(a);
 
	getch();
	return 0;
}

Vấn đề là mình ĐÃ thêm được thành công, nhưng khi thêm thì nó kèm theo 1 bảng xuất hiện với dòng chữ khá đẹp đẽ như bức hình sau:

Mọi người ai biết lỗi giúp mình nhé, xin cảm ơn

viết 01:38 ngày 01/10/2018

int **a = (int **)malloc(dong * sizeof(int));

phải là sizeof(int*) mới đúng. May mắn ở đây là size của con trỏ = size của int nên dòng này ko gây lỗi. a là con trỏ tới con trỏ nên dong * sizeof... thì ... phải là kiểu mà a trỏ tới, ở đây là kiểu con trỏ nên phải viết là dong * sizeof(int*)

sau dòng malloc này thì đã tạo được 1 mảng 1 chiều chứa con trỏ, tại địa chỉ nào đó, ví dụ là 0x100, và a có giá trị là địa chỉ 0x100 này:

0x100: [???][???]
a = 0x100

sau đó gọi malloc cho từng a[i]:

0x100: [0x1248][0x1348]
a = 0x100
...
0x1248: [???][???][???]
0x1348: [???][???][???]

sau khi nhập giá trị ma trận:

0x100: [0x1248][0x1348]
a = 0x100
...
0x1248: [1][2][3]
0x1348: [4][5][6]

khi thêm dòng, vì tại 0x100 chỉ đủ chỗ cho 2 con trỏ nên ko thể thêm dòng thứ 3 (con trỏ thứ 3) được, hay viết a[dong] với dong = 2 là sai. Phải cấp phát chỗ khác đủ chỗ cho 3 dòng/3 con trỏ:

int** new_a =  = (int**)malloc(dong+1, sizeof(int*));
0x100: [0x1248][0x1348]
0x200: [???][???][???]
a = 0x100
new_a = 0x200
...
0x1248: [1][2][3] //y nguyên, ko thay đổi
0x1348: [4][5][6] //y nguyên, ko thay đổi

copy giá trị mảng cũ sang mảng mới:

for (int i = 0; i < dong; ++i) new_a[i] = a[i];
0x100: [0x1248][0x1348]
0x200: [0x1248][0x1348][???]
a = 0x100
new_a = 0x200
...
0x1248: [1][2][3]
0x1348: [4][5][6]

xóa mảng cũ:

free(a);
a = NULL;
0x200: [0x1248][0x1348][???]
a = 0
new_a = 0x200
...
0x1248: [1][2][3]
0x1348: [4][5][6]

cho a trỏ vào mảng mới

a = new_a;
0x200: [0x1248][0x1348][???]
a = 0x200
...
0x1248: [1][2][3]
0x1348: [4][5][6]

rồi thêm dòng mới vào: a[2] = (int*)malloc(cot * sizeof(int));

0x200: [0x1248][0x1348][0x1448]
a = 0x200
...
0x1248: [1][2][3]
0x1348: [4][5][6]
0x1448: [?][?][?]

rồi nhập giá trị cho dòng mới…

vì ở đây giá trị của a thay đổi khi gọi dòng a = NULL hay a = new_a nên phải truyền tham chiếu cho a: void ThemDong(int **&a, ...

vì 1 con trỏ có thể đại diện cho 1 dòng ở đây, nên khi swap 1 dòng thì ko phải mất công copy từng phần tử, chỉ cần swap giá trị 2 con trỏ là được. Ví dụ

0x200: [0x1248][0x1348][0x1448] //mảng ban đầu
0x200: [0x1448][0x1348][0x1248] //swap dòng thứ nhất với dòng thứ 3, chỉ cần swap con trỏ
Người bí ẩn viết 01:37 ngày 01/10/2018

khi thêm dòng, vì tại 0x100 chỉ đủ chỗ cho 2 con trỏ nên ko thể thêm dòng thứ 3 (con trỏ thứ 3) được, hay viết a[dong] với dong = 2 là sai. Phải cấp phát chỗ khác đủ chỗ cho 3 dòng/3 con trỏ.

Vậy cái chỗ này em làm bằng cách khác, đó làm thêm dòng: realloc(a, (dong + 1) * sizeof(int *)); vào đầu hàm Thêm Dòng là hoàn toàn được chứ ạ ?
Em thử Build thì ok rồi, ý anh @tntxtnt như thế nào ?

P/S: Sao em #include <algorithm> rồi mà nó vẫn không có hàm swap để dùng nhỉ, phải tự gõ hàm Hoán Vị mệt quá @@

viết 01:42 ngày 01/10/2018

ghi đầy đủ std::swap xem

realloc cũng được, thậm chí còn có thể tốt hơn, nhưng vẫn phải truyền tham chiếu cho a (int**& a). (viết **& nhiều * hoa mắt quá )

thêm cái nữa:
*(*(a + i / cot) + i % cot)) trong hàm XuatMaTran thì viết là a[i/cot][i%cot] cho nó dễ đọc… scanf trong NhapMaTran cũng vậy: &a[i/cot][i%cot] (thêm dấu & để lấy địa chỉ vô phía trước)

Người bí ẩn viết 01:30 ngày 01/10/2018

Anh @tntxtnt ơi,

Sau khi em thêm dòng: realloc(a, (dong + 1) * sizeof(int *)); vào đầu hàm ThemDong thì code chạy bình thường, không có vấn đề gì cả !

Nhưng khi nhập ma trận 3 x 4 và thêm vào dòng 1 thì nó lỗi như sau, em không hiểu luôn @@

Update Source Code: http://codepad.org/9zDNmyPn

void NhapMaTran(int **a, int dong, int cot)
{
	for (int i = 0; i < dong * cot; i++)
	{
		printf("\nNhap a[%d][%d] = ", i / cot, i % cot);
		scanf("%d", *(a + i / cot) + i % cot);
	}
}
void XuatMaTran(int **a, int dong, int cot)
{
	for (int i = 0; i < dong * cot; i++)
	{
		printf("%5d", *(*(a + i / cot) + i % cot));
		if ((i + 1) % cot == 0)
			printf("\n");
	}
}
void HoanVi(int &a, int &b)
{
	int Temp = a;
	a = b;
	b = Temp;
}
void SwapLine(int **a, int cot, int kdong1, int kdong2)
{
	for (int i = 0; i < cot; i++)
	{
		HoanVi(a[kdong1][i], a[kdong2][i]);
	}
}
void ThemDong(int **&a, int &dong, int cot, int kdong, int *b)
{
	realloc(a, (dong + 1) * sizeof(int *));
	a[dong] = (int *)malloc(cot * sizeof(int));
	for (int i = dong; i > kdong; i--)
	{
		SwapLine(a, cot, i - 1, i);
	}
	dong++;
	for (int i = 0; i < cot; i++)
	{
		a[kdong][i] = b[i];
	}
}
int main()
{
	int dong, cot;
	do
	{
		printf("\nNhap so luong dong: ");
		scanf("%d", &dong);
		printf("\nNhap so luong cot: ");
		scanf("%d", &cot);
		if (dong < 1 || cot < 1)
			printf("\nSo luong dong hoac cot khong hop le\n");
	} while (dong < 1 || cot < 1);
	int **a = (int **)malloc(dong * sizeof(int));
	for (int i = 0; i < dong; i++)
		a[i] = (int *)malloc(cot * sizeof(int));
 
	NhapMaTran(a, dong, cot);
	XuatMaTran(a, dong, cot);
 
	int kdong;
	do
	{
		printf("\nNhap chi so dong can them: ");
		scanf("%d", &kdong);
		if (kdong < 0 || kdong > dong)
			printf("\nChi so dong can them khong hop le\n");
	} while (kdong < 0 || kdong > dong);
	int *b = (int *)malloc(cot * sizeof(int));
	for (int i = 0; i < cot; i++)
	{
		printf("\nNhap a[%d][%d] = ", kdong, i);
		scanf("%d", &b[i]);
	}
	printf("\nMa tran sau khi them dong %d:\n", kdong);
	ThemDong(a, dong, cot, kdong, b);
	XuatMaTran(a, dong, cot);
 
	for (int i = 0; i < dong; i++)
		free(a[i]);
	free(a);
	
	getch();
	return 0;
}
viết 01:29 ngày 01/10/2018

realloc(a, (dong + 1) * sizeof(int *));

phải là a = (int**) realloc(a, (dong + 1) * sizeof(int *));

Người bí ẩn viết 01:38 ngày 01/10/2018

Yeah được rồi !!!

phải là a = (int**) realloc(a, (dong + 1) * sizeof(int *));

Hồi em học bên con trỏ cấp 1 thì nó chỉ cần mỗi dòng realloc(a, (some_thing + 1) * sizeof(some_thing)); là được, mà ở đây sao mình lại phải để thêm a đằng trước nữa nhỉ (giống anh ý) ?

viết 01:30 ngày 01/10/2018

void* realloc(void* ptr, ..._ với tham số kiểu này thì giá trị của ptr truyền vào đâu có thay đổi được.

realloc nó kiểm tra xem phía sau ptr còn đủ chỗ trống ko, nếu còn thì vẫn giữ nguyên mảng ở địa chỉ cũ, chỉ cần tăng kích cỡ của mảng cũ lên, nên giá trị ptr ko thay đổi vẫn chạy đúng (trường hợp này giả về địa chỉ mảng cũ nên gán a = realloc... chả khác gì ko gán realloc...).

còn nếu ko đủ chỗ trống thì phải dời mảng cũ sang địa chỉ mới, copy giá trị mảng cũ sang mảng mới, rồi xóa mảng cũ, rồi trả về con trỏ tới mảng mới. Vì ptr truyền tham số nên giá trị nó ko thay đổi được, phải gán giá trị mới lại cho nó a = realloc...

Người bí ẩn viết 01:39 ngày 01/10/2018

Ừm, hiểu được xíu :))


Anh Trí xem giúp em luôn 2 cái hàm Thêm Cột và Xóa Cột à, nó cũng bị lỗi

void ThemCot(int **&a, int dong, int &cot, int kcot, int *c)
{
	for (int i = 0; i < dong; i++)
	{
		a[i] = (int *)realloc(a[i], (cot + 1) * sizeof(int));
	}
	for (int i = 0; i < dong; i++)
		a[i][kcot] = c[i]; // mảng C chứa các giá trị của cột cần thêm
	for (int x = 0; x < dong; x++)
	{
		for (int i = cot; i > kcot; i--)
		{
			std::swap(a[x][i], a[x][i - 1]);
		}
	}
	++cot;
}
void XoaCot(int **&a, int dong, int &cot, int cotxoa)
{
	for (int i = 0; i < dong; i++)
	{
		for (int j = cotxoa; j < cot - 1; j++)
			std::swap(a[i][j], a[i][j + 1]);
	}
	for (int i = 0; i < dong; i++)
		realloc(a[i], (cot - 1) * sizeof(int));
	--cot;
}
viết 01:30 ngày 01/10/2018

chỗ thêm cột phải dời mấy phần tử cũ đi trước khi copy phần tử mới vào:

vd dòng x có các phần tử là 1 2 3 4 5, cần chèn số 9 vào sau cột thứ 3:

1 2 3 4 5 ?  //realloc
1 2 3 4 ? 5  //lần lượt dời các phần tử phía sau cột thứ 3 qua phải 1 cột
1 2 3 ? 4 5  //dời xong
1 2 3 9 4 5  //gán a[3] = 9

lôi cái swap lên trước cái gán.

Người bí ẩn viết 01:34 ngày 01/10/2018

À, fix được rồi, thanks anh nhiều

viết 01:43 ngày 01/10/2018
    for (int x = 0; x < dong; x++)
    {
        for (int i = cot; i > kcot; i--)
        {
            std::swap(a[x][i], a[x][i - 1]);
        }
    }
    for (int i = 0; i < dong; i++)
	a[i][kcot] = c[i]; // mảng C chứa các giá trị của cột cần thêm

dồn chỗ trước rồi mới gán sau

Người bí ẩn viết 01:31 ngày 01/10/2018

Great


Anh Trí có cách nào để cải thiện code cho em không ?

P/S: Còn hàm Xóa Cột là ổn rồi chứ nhỉ @tntxtnt

viết 01:40 ngày 01/10/2018

cải thiện thì chắc phải viết lại từ đầu =))

bắt đầu viết với mảng 1 chiều, thêm/xóa 1 phần tử, viết ra nhiều hàm nhỏ. Rồi đi lên mảng 2 chiều, reuse mấy hàm nhỏ này.

Người bí ẩn viết 01:36 ngày 01/10/2018

Anh @tntxtnt xem giùm em đoạn code sau nhé

Đề bài: Thêm / Xóa Dòng / Cột trong ma trận cho trước (có kiểu dữ liệu bất kì)
Em làm hàm Thêm Dòng trước nhưng bị lỗi

Source code: http://codepad.org/fazrK5NG

template <class minh>
void NhapMaTran(minh **a, int dong, int cot)
{
	for (int i = 0; i < dong * cot; i++)
	{
		std::cout << "\nNhap a[" << i / cot << "][" << i % cot << "] = ";
		std::cin >> *(*(a + i / cot) + i % cot);
	}
}
template <class minh>
void XuatMaTran(minh **a, int dong, int cot)
{
	for (int i = 0; i < dong * cot; i++)
	{
		std::cout << *(*(a + i / cot) + i % cot) << "    ";
		if ((i + 1) % cot == 0)
			std::cout << std::endl;
	}
}
template <class minh>
void ThemDong(minh **&a, int &dong, int cot, int kdong, minh *mangthem)
{
	a = (minh **)realloc(a, (dong + 1) * sizeof(minh)); // dùng tạm hàm realloc trong C
	a[dong] = new minh[cot];
	for (int i = 0; i < cot; i++)
		a[dong][i] = mangthem[i];
	for (int i = dong; i > kdong; i--)
		std::swap(a[i], a[i - 1]);
	++dong;
}
template <class minh>
int main()
{
	/*------------------------------ NHẬP / XUẤT MA TRẬN------------------------------*/
 
	int dong, cot;
	do
	{
		std::cout << "\nNhap so luong dong: ";
		std::cin >> dong;
		std::cout << "\nNhap so luong cot: ";
		std::cin >> cot;
		if (dong < 1 || cot < 1)
			std::cout << "\nSo luong phan tu khong hop le\n";
	} while (dong < 1 || cot < 1);
	char **a = new char*[dong];
	for (int i = 0; i < dong; i++)
		a[i] = new char[cot];
	NhapMaTran(a, dong, cot);
	XuatMaTran(a, dong, cot);
 
 
	/*------------------------------ THÊM / XÓA DÒNG TRONG MA TRẬN------------------------------*/
 
	int kdong;
	do
	{
		std::cout << "\nNhap dong can them: ";
		std::cin >> kdong;
		if (kdong < 0 || kdong > dong)
			std::cout << "\nChi so dong can them khong hop le\n";
	} while (kdong < 0 || kdong > dong);
	minh *mangthem = new minh[cot];
	for (int i = 0; i < cot; i++)
	{
		std::cout << "\nNhap a[" << kdong << "][" << i << "] = ";
		std::cin >> mangthem[i];
	}
	ThemDong(a, dong, cot, kdong, mangthem);
	std::cout << "\nMa tran sau khi them dong " << kdong << ":\n";
	XuatMaTran(a, dong, cot);
	
	for (int i = 0; i < dong; i++)
		delete[] a[i];
	delete[] a;
	system("pause");
	return 0;
}

Còn đây là lỗi nó thông báo ! Điều đặc biệt là cái lỗi này nó này ở file khác chứ không phải ở file source.cpp

viết 01:34 ngày 01/10/2018

a là con trỏ trỏ tới con trỏ. a trỏ tới mảng con trỏ. a ko phải là mảng 2 chiều. Đừng nghĩ a là mảng 2 chiều khi cấp phát động cho nó. Nghĩ nó là mảng con trỏ. Cấp phát 10 con trỏ cho nó. Cấp phát 20 con trỏ cho nó.

a là mảng con trỏ sao lại đi realloc cho nó sizeof(minh) ? a có phải là mảng minh đâu?

còn nữa, phải dời mấy dòng cũ đi để có chỗ cho dòng mới, rồi mới cấp phát dòng mới cho a[dong] được.

        //quẳng cái này lên trước a[dong] = new...
	for (int i = dong; i > kdong; i--)
		std::swap(a[i], a[i - 1]);

phía trước int main() ko cần thêm template <...>

à khi xài new/delete thì đừng nên xài chung với realloc…

EDIT: phải là a[kdong] = new… mới đúng ~.~

http://rextester.com/KPDT35345

Người bí ẩn viết 01:38 ngày 01/10/2018

còn nữa, phải dời mấy dòng cũ đi để có chỗ cho dòng mới, rồi mới cấp phát dòng mới cho a[dong] được.

EDIT: phải là a[kdong] = new… mới đúng ~.~

Hình như cái chỗ này em đâu có sai ?
Chỉ sai ở chỗ template trong hàm Main và chỗ a = (minh **)realloc(...) thôi !

à khi xài new/delete thì đừng nên xài chung với realloc…

Về vấn đề này, em xóa cái template trong hàm Main đi và tự tạo 1 hàm realloc cho mình rồi sửa lại => CODE chạy ok rồi

FINALLY

a là mảng con trỏ sao lại đi realloc cho nó sizeof(minh) ? a có phải là mảng minh đâu?

Còn mỗi chỗ này em chưa hiểu ? Anh Trí có thể “thông não” giúp em đoạn này không ? Đại khái là nói sâu và dài + dễ hiểu hơn xíu nữa

viết 01:30 ngày 01/10/2018

a là mảng con trỏ
a -> [địa chỉ dòng 1][địa chỉ dòng 2][địa chỉ dòng 3]

mà địa chỉ mỗi dòng có kích cỡ bằng với kích cỡ con trỏ sizeof(T*) chứ ko phải sizeof(T). Kích thước 1 con trỏ có thể là 4 hoặc 8 bytes, còn kích thước của T thì bất kì, có thể 1 byte, 2 bytes, 3 bytes, hoặc 1000 bytes. Nhân cho sizeof(T) thì sai rồi.

vd char** a = new char*[4]; thì a -> […][…][…][…] (1 dấu chấm là 1 byte)
nhưng char** a = new char[4]; thì a -> [.][.][.][.]
hoặc SinhVien** a = new SinhVien[4];, 1 SinhVien có kích cỡ 24 bytes, thì a -> […][…][…][…]
còn SinhVien** a = new SinhVien*[4];, thì a -> […][…][…][…]

Bài liên quan
0