leetcode 刷起!!!!

           今天终于开始刷Leetcode了。


           开始用的facebook账号登陆的,提示要发送验证邮箱通过验证之后才能在上面跑代码,坑的是,我点了一下,提示发送成功,一分钟邮箱没有收到邮件,我再点一下,再一下一下......下......,特么的还是没收到,我又申请一个邮箱,再注册一个账号,然后还是一下.....一下....下....还是不行。然后换了个网络同样不行。。于是各种谷歌,结果,半个小时之后几个邮件接着都来了,搞不懂为毛这么慢?!我还以为我网络出什么毛病了呢= = !


          接着开始拉到最底下,开始从第一题慢慢做起,发现Medium的题也还算简单,不过时间主要不是卡在算法上面,而是C++   STL库中各种工具,由于初学C++,各种库各种函数都不知道,不会用啊,这最累人了,算法一早就想好了,却还一直在搜库函数的用法= = ,没办法,就当做学习吧。也确实,这几个小时,虽然才刷了2题,但至少接触了几个库函数,还稍微弄清楚了些局部变量存活的问题,收获挺大的。    之后会继续坚持!!!


下面贴上关于C++函数返回局部变量的相关问题,原文http://www.samirchen.com/function-returns/



函数返回局部变量的时候会遇到各种各样的情况,涉及到内存相关的东西时一定要小心是否会出错。

一般来说,在函数内对于存在栈上的局部变量的作用域只在函数内部,在函数返回后,局部变量的内存已经释放。因此,如果函数返回的是局部变量的值,不涉及地址,程序不会出错;但是如果返回的是局部变量的地址(指针)的话,就造成了野指针,程序运行会出错,因为函数只是把指针复制后返回了,但是指针指向的内容已经被释放了,这样指针指向的内容就是不可预料的内容,调用就会出错。

 
 
  1. #include <iostream>
  2. using namespace std;
  3. int fun1() {
  4. int i = 1;
  5. return i; // OK.
  6. }
  7. int* fun2() {
  8. int i = 2;
  9. int* ip = &i;
  10. return ip; // Wrong!
  11. }
  12. int main() {
  13. int r1 = fun1();
  14. cout << r1 << endl; // 1
  15. int* r2 = fun2();
  16. cout << *r2 << endl; // 这里有可能可以打印出结果:2,看似正确的,但其实是有问题的。这是因为相应的内存还未被覆盖,但这块内存已经是自由的、不被保护的了。
  17. return 0;
  18. }

先看代码:

 
 
  1. #include <iostream>
  2. using namespace std;
  3. char* fun3() {
  4. char* s = "Hello";
  5. return s; // OK.
  6. }
  7. char* fun4() {
  8. char s[] = "Hello";
  9. return s; // Wrong!
  10. }
  11. int main() {
  12. char* r3 = fun3();
  13. cout << r3 << endl; // Hello
  14. char* r4 = fun4();
  15. cout << r4 << endl; // 内存已经无效的了。打印出乱码。
  16. return 0;
  17. }

通过 char* s = "Hello"; 的方式得到的是一个字符串常量 Hello,存放在只读数据段(.rodata section),把该字符串常量的只读数据段的首地址赋值给了指针 s,所以函数返回时,该字符串常量所在的内存不会被回收,所以能正确地通过指针访问。 通过 char s[] = "Hello"; 的方式得到的是局部变量,字符串直接量作为基于栈的字符数组的初始值,这里得到的字符数组 s 实际的数据存储: H e l l o \0。增加了一个终结符 \0。所以函数返回时,栈被清空,局部变量内存清空,返回的是一个已被释放的内存的地址,打印出来的就会是乱码。

如果函数的返回值非要是一个局部变量地址,可以把局部变量声明为static静态变量。这样变量存储在静态存储区,程序运行过程中一直存在。

 
 
  1. #include <iostream>
  2. using namespace std;
  3. int* fun5() {
  4. static int i = 5;
  5. return &i; // OK.
  6. }
  7. char* fun6() {
  8. static char s[] = "Hello";
  9. return s; // OK.
  10. }
  11. int main() {
  12. int* r5 = fun5();
  13. cout << *r5 << endl; // 5
  14. char* r6 = fun6();
  15. cout << r6 << endl; // Hello
  16. return 0;
  17. }

数组是不能作为函数的返回值的。因为编译器会把数组名认为是局部变量(数组)的地址。返回一个数组,实际上是返回指向这个数组首地址的指针。函数结束后,数组作为局部变量被释放,这个指针则变成了野指针。同1的fun2()及2的fun4()(字符数组)

但是声明数组是静态的,然后返回是可以的,同3。

 
 
  1. #include <iostream>
  2. using namespace std;
  3. int* fun7() {
  4. int a[3] = {1, 2, 3};
  5. return a; // Wrong!
  6. }
  7. int* fun8() {
  8. static int a[3] = {1, 2, 3};
  9. return a; // OK.
  10. }
  11. int main() {
  12. int* r7 = fun7();
  13. cout << *r7 << endl; // 内存已经是无效的了。
  14. int* r8 = fun8();
  15. cout << *r8 <<endl; // 1
  16. return 0;
  17. }

函数返回指向存储在堆上的变量的指针是可以的。但是,程序员要自己负责在函数外释放(free/delete)分配(malloc/new)在堆上的内存。

 
 
  1. #include <iostream>
  2. using namespace std;
  3. char* fun9() {
  4. char* s = (char*) malloc(sizeof(char) * 100);
  5. return s; // OK. 但需要程序员自己释放内存。
  6. }
  7. int main() {
  8. char* r9 = NULL;
  9. r9 = fun9();
  10. strcpy(r9, "Hello");
  11. cout << r9 << endl; // Hello
  12. free(r9); // 要记得自己释放内存。
  13. return 0;
  14. }

函数内局部对象的引用是不能作为函数返回值的。函数内的局部对象在离开函数作用域后会被自动析构(自动调用其析构函数),则它的引用不再指向一个有效对象,结果无法预测。就类似1的fun2()返回局部变量地址后,这个地址指向的内存其实已经不再有效了。

函数内用new创建的堆上的对象的指针是可以作为函数返回值的,但是类似5要程序员自己负责在函数外释放对象内存。

classa.h

 
 
  1. #ifndef CLASSA_H
  2. #define CLASSA_H
  3. class ClassA
  4. {
  5. public:
  6. int x;
  7. int y;
  8. int z;
  9. public:
  10. ClassA(int xx, int yy, int zz);
  11. ~ClassA();
  12. void printXYZ();
  13. };
  14. #endif // CLASSA_H

classa.cpp

 
 
  1. #include "classa.h"
  2. #include <iostream>
  3. using namespace std;
  4. ClassA::ClassA(int xx, int yy, int zz) : x(xx), y(yy), z(zz) {
  5. cout << "Construct a ClassA" << endl;
  6. }
  7. ClassA::~ClassA() {
  8. cout << "Deconstruct a ClassA" << endl;
  9. }
  10. void ClassA::printXYZ() {
  11. cout << x << " " << y << " " << z << endl;
  12. }

main.cpp

 
 
  1. #include <iostream>
  2. using namespace std;
  3. ClassA& fun10() {
  4. ClassA ca(1, 2, 3);
  5. return ca; // Wrong!
  6. }
  7. ClassA* fun11() {
  8. ClassA* ca = new ClassA(1, 2. 3);
  9. return ca; // OK. 但需要程序员自己释放内存。
  10. }
  11. int main() {
  12. ClassA r10 = fun10();
  13. r10.printXYZ(); // 对象在函数返回时已经被析构了,对应的内存已经无效。
  14. ClassA* r11 = fun11();
  15. r11->printXYZ(); // 1 2 3
  16. delete r11; // 要记得自己释放内存。
  17. return 0;
  18. }

总结上面的几个例子,我们只要搞清楚函数返回的东西是什么、其对应的内存存在哪一般就可以搞清楚它能不能被返回,其实有的例子印证的结果是重叠的。

一个完整的程序,内存分布情况:program-memory-distribution

上面的几个例子中,我们返回的东西包括:

  • 栈上变量值(基本类型)可以作为函数返回值;因为只涉及到传值,不会有什么问题;
  • 栈上变量地址(基本类型指针;数组;对象引用)不能在作为函数返回值;因为它们的作用域只在函数内,函数返回后它们的内存都不再有效;
  • 堆上变量地址(malloc或new得到的指针)可以作为函数返回值;变量存在堆上,其内存从分配后一直有效直到程序员自己释放(free或delete);
  • 静态变量地址(static)可以作为函数返回值;因为它们存储在静态变量区,其内存整个程序运行期间一直有效;
  • 只读数据段(.rodata)地址(字符串常量)可以作为函数返回值;因为它们存储在只读数据段,其内存整个程序运行期间一直有效;

以上内容,难免有差错纰漏,欢迎大家指正!


当你开始刷LeetCode题目时,以下是一些建议和指南,特别是在使用C++语言时: 1. 熟悉数据结构和算法:在开始刷题之前,确保你对常见的数据结构(例如数组、链表、栈、队列、树、图等)和算法(例如排序、搜索、递归、动态规划等)有一定的了解。 2. 学习C++ STLSTL(Standard Template Library)是C++标准库中非常强大和实用的部分。掌握STL中的各种容器(如vector,list,map等)和算法(如sort,binary_search,lower_bound等)将帮助你更轻松地解决LeetCode问题。 3. 阅读题目描述和要求:在开始解题之前,仔细阅读题目描述和要求,确保你理解问题的意思和所需的输出。 4. 设计和实现解决方案:使用你所学过的数据结构和算法知识,设计一个解决方案。可以先在纸上画出算法的步骤,再逐步将其转化为代码。 5. 编写测试用例:在实现解决方案之前,编写一些测试用例来验证你的代码是否正确。包括一些边界情况和特殊情况。 6. 调试和优化代码:如果你的代码无法通过所有的测试用例,使用调试工具来找出问题所在。同时,尝试优化你的代码,使其更加高效和可读性。 7. 学习其他人的解决方案:在解决一个问题后,可以查看其他人的解决方案,学习他们的思路和技巧。这有助于拓宽你的解题思路和提高编程技巧。 8. 坚持刷题:刷题需要持续的练习和积累。建议每天刷几道题目,并且尽可能多地涵盖不同类型和难度的题目。 希望这些建议对你在LeetCode上刷题时有所帮助!祝你成功!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值