数组, 指针数组, 指向指针的指针的数组, ….
在逛Cplusplus Forum - Beginner的时候发现了一个看上去看简单的问题
How do I receive two numbers from the keyboard, save them in an array of two element?
第一次尝试
void f0() { // ERROR: 先存放再输入不会修改数组中的数据 |
我想得太简单了。不能在标准输入中, 直接对数据进行修改。
下面给出的答案
1,void f1() {
int iarr[2] = {};
std::cin >> iarr[0] >> iarr[1]; // 直接输入到数组中
}
2,void f2() {
int a, b;
std::cin >> a >> b; // 先输入, 再存放
int iarr[2] = {a, b};
}
但是我仍然考虑我思路实现的可能性?
对已经放置在数组中的值进行修改的思路如何实现?
第一个想到的就是指针。
void f3() { // BUG: 为什么不能通过数组中保存的指针修改数组?? |
$ ./a.out |
很气, 我得出结论。不能通过数组中保存的指针修改数组。
数组作为函数形参
接着, 我想起之前在函数中通过数组改变过值, 便想要实现如下任务:
创建一个函数, 输入一个保存指向整数的指针的数组, 修改索引n位置的指针所指向的值, 返回
void revise_value_int(int* arr, int n) { |
可以改变。但是仔细一想, 我仅仅只是在数组中存放了整数。
函数形参为存放指针的数组
接着,我改变了数组,用于存放指针int* pa, *pb, *pc;
int ptrArr[] = {pa, pb, pc};
编译时就出现了错误,.error: invalid conversion from ‘int’ to ‘int’ [-fpermissive]
因为现在数组保存着的是指向整型的指针, 因此数组的类型应该是 int\
修改int* pa, *pb, *pc;
int* ptrArr[] = {pa, pb, pc};
写修改指针所指向整数的函数, 注意, 这个形参类型是int**
, ptrArr是指向指针的指针, 调用数组, 实际上是调用指向数组首元素的指针。void revise_value_ptr(int** ptrArr, int n) {
// 索引为n的指向的指针所指向的整数递增1
(**(ptrArr+n))+=1;
}
Segmentation fault (core dumped)
再次编译Segmentation fault (core dumped)
出现 Segmentation fault (core dumped) 的原因, 参考[http://blog.sina.com.cn/s/blog_4b9eab320100uivj.html]
1 内存访问越界
a) 由于使用错误的下标,导致数组访问越界
b) 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符
c) 使用strcpy, strcat, sprintf, strcmp, strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。
2 多线程程序使用了线程不安全的函数。
3 多线程读写的数据未加锁保护。
对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成core dump
非法指针
a) 使用空指针
b) 随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它时就很容易因为bus error而core dump.
堆栈溢出
不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误。
非法指针
我的代码是:int* pa, *pb, *pc;
int* ptrArr[] = {pa, pb, pc};
这里我使用了非法的指针, 这个指针没有指向任何
修改,
void call_revise() { |
编译得到**(ptrArr) = 1
**(ptrArr+1) = 2
**(ptrArr+2) = 4
解决最开始问题并总结
修改后的代码void f4() { // DEBUG: 先存放再输入不会修改数组中的数据
int a, b;
int* pa = &a, int* pb = &b; // 注意: 不要定义空指针
int* iarr[2] = {pa, pb}; // 注意数组的类型是数组中元素的类型
std::cin >> a >> b; // 也能合理:先存放, 再输入
std::cout << **iarr << ' ' << **(iarr+1) << std::endl; // 解引用
// 4197661 0
}
不要定义空指针
如这样int* pa;
即使要定义也要int *pa = 0;
int *pb = nullptr;
尽量让指针指向某个对象int *pa = &a;
数组的类型
数组的类型是数组中元素的类型int* iarr[2] = {pa, pb}; // 类型为 int*
解引用数组
int* iarr[2] = {pa, pb}; // 如果数组这样定义 |
标准输出它们得到iarr = 0x7ffed5caa560
*iarr = 0x7ffed5caa548
**iarr = 1
更多的尝试
数组中存放对象
template <typename T> |
Name = tim |
面向对象可以让我从无尽的指针中抽身出来, 关注相互之间的联系。
数组中存放长度不同的数组
void f_array() { |
对于简单的数组, 打印储存不同长度数组的数组, 需要更多的数据结构知识, 而且不同的算法还有好坏, 可见数据结构的重要性. 这部分的打印实现, 留作日后我数据结构解引用后的小试牛刀。
感想
即使从一个很小很简单的问题入手, 也能对自己有所提高, 计算机科学没有简单, 复杂是由简单构成的, 计算机科学承认复杂, 计算机科学尝试破译复杂。
常量成员函数
class MyClass { |
声明时引用符号总最靠近变量名
为什么可以对a.get()赋值
get()返回的是引用, 引用可作为左值, 且如果非常量引用的话, 该值可以被修改。
在是否为成员函数上重载后如何匹配?
成员函数在是否为常量函数上可以重载。如果对象实例化成const类型, 则匹配常量成员函数。
const int& get() const {return x;} 为什么返回类型需要const?
常量函数返回数据成员, 编译器会将数据成员声明成const类型;
什么是数据成员?
MyClass类public中定义新成员测试是否为数据成员。
//.. |
改变a的值无法通过编译, a为数据成员。
在class中声明的, 无论是私有的还是公有的数据, 都是成员数据。如果在常量函数中返回, 会常量函数会被声明成const类型。
template基础用法
typename T 和 class T的区别
在模板参数列表中, typename和class没有什么不同。 但每个参数类型前必须加上typename 或者 class
<<C++ Primer 5th>> P580 模板与泛型编程
特殊化模板
template <typename T> |
对char类型, 有特定的方法uppercase()大写化字母template <>
class mycontainer<char> {
char element;
public:
mycontainer(const char e): element(e) {}
char increase() {return ++element;} // 递增char
char getElement() const {return element;} // 返回element数据
char uppercase() {
if (element <= 'z' && element >= 'a') { // 是小写字母
element += 'A' - 'a'; return element;
} // 返回元素
std::cout << "element " << element << "is not lower" << std::endl;
return element;
}
};
格式如下template <typename T> class mycontainer {...}
template <> class mycontainer<char> {...}