const常引用(const + &)避免函数参数的双向传递
在c++可以使用引用传递作为函数的形参传入函数,相较于值传递的方式,引用传递能够节省函数使用时的内存分配,不需要像值传递一样拷贝实参。对于普通的数据类型可能看出引用的优势,但是如果函数的传入参数是一个十分复杂的结构体或者类,那么引用传递可以节省很大的内存开销。
然而,由于引用传递是双向的,当在函数中对于形参的数据进行改变后,实参的值也会进行相应的改变,如下所示:
#include <iostream>
using namespace std;
struct Point
{
int x;
int y;
Point(int a, int b)
{
x=a;
y=b;
}
};
void fun(Point& point);
int main()
{
Point point(1,1);
fun(point);
point.x++;
point.y++;
cout << "======main======" << endl;
cout << "点的坐标为(" << point.x << "." << point.y << ")" << endl;
return 0;
}
void fun(Point& point)
{
point.x++;
point.y++;
cout << "======fun======" << endl;
cout << "点的坐标为(" << point.x << "," << point.y << ")" << endl;
}
/*
------fun-------
点的坐标为(2,2)
------main------
点的坐标为(2,3)
如果我们既不想改变传入参数的值,也不想因为值传递产生太大的开销,那么可以尝试一下使用常引用。可见,使用了常引用之后,传入参数的值就是一个常量了,无法对其内部变量进行修改,保证了传入参数的数据安全性。
这里引用的作用主要是为了避免值传递,值传递通常会有很大的开销。
C语言三个结束符有什么不同? EOF ‘\0’ ‘\n’
网友A:
EOF(End of file)是C/C++里面的宏定义,具体定义式是#define EOF -1,表示的是文件的结束标志,值等于-1,一般用在文件读取的函数里面,比如fscanf fgetc fgets等,一旦读取到文件最后就返回EOF标志并结束函数调用 ‘\0’是转义字符,值等于0,主要用在C风格字符串的末尾,表示字符串结束标志。通常用在和字符串相关的函数里面,如strcmp strcpy等会用到它 ‘\n’表示换行符,通常用作一些读取函数的读取结束标志,比如scanf,getchar(),gets()等,一旦遇到’\n’就结束读取并返回
网友B:
EOF 是一个宏定义,一般是-1,用在读文件的时候.因为如果读到字符,这个字符的值一定是正的,所以用负值表示结束 \0 是ascii码为0,一般表示用在字符串结尾表示空值.一个char a[100]数组,当你用这个数组进行字符串操作时,会把\0当做结尾.如果没有设置\0标志,这个字符串很可能出现问题 \n 好像ascii码是10吧,就是回车的意思,a是1个字符,c也是1个字符,同样的,回车也是1个字符,只不过表现得不那么正常而已
extern “C”
#ifndef __INCvxWorksh /*防止该头文件被重复引用*/
#define __INCvxWorksh
#ifdef __cplusplus //__cplusplus是cpp中自定义的一个宏
extern "C" { //告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
#endif
/**** some declaration or so *****/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */
2、被extern “C"修饰的变量和函数是按照C语言方式编译和链接的 首先看看C++中对类似C的函数是怎样编译的。 作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为: void foo( int x, int y ); 该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。 ** _foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。** 例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。 同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以”.“来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。
while(scanf("%d”,&n)!=EOF) 用法
EOF(end of file)就是文件的结束,通常来判断文件的操作是否结束的标志。
EOF不是特殊字符,而是定义在头文件<stdio.h>的常量,一般等于-1;
#include<stdio.h>
int main(){
char str[100][100];
int i=0,j;
while(scanf("%s", str[i]) != EOF)
//在黑框中手动输入时,系统并不知道什么时候到达了所谓的“文件末尾“
//因此需要用< Ctrl + Z >组合键,然后按< Enter >键的方式来告诉系统已经到了 EOF,这样系统才会结束 while
i++; //while((str[i]=getchar())!='\n')
for(j=i-1;j>=0;j--){
printf("%s",str[j]);
if(j!=0)
printf(" ");
}
return 0;
}
除了文件结束,做题遇见最多的是标准输入,但是标准输入与文件不一样,无法事先知道输入的长度,必须手动输入一个字符,表示到达EOF:
Linux中,在新的一行的开头,按下Ctrl-D,就代表EOF(如果在一行的中间按下Ctrl-D,则表示输出“标准输入”的缓存区,所以这时必须按两次Ctrl-D);
Windows中,Ctrl-Z表示EOF。
结构体可以用作 map
的键吗?
答: 可以,结构体是可以作为 map
的键的,但需要满足一定的条件。首先 map
的底层结构是 红黑树
,属于 平衡二叉查找树
。对于map来说, key必须是有序的, 也就是说, key与key之间必须能比较, 所以需要重载 <
号 。所以当结构体作为 map
的键时,必须要重载 <
运算符。
#include <iostream>
#include <string>
#include <map>
using namespace std;
struct Info
{
string name;
int score;
// 重载 < 运算符
bool operator< (const Info &x) const
{
return score < x.score;
}
};
int main()
{
Info a, b;
a.name = "eric";
a.score = 90;
b.name = "cat";
b.score = 85;
map<Info, int> m;
m[a] = 1;
m[b] = 2;
map<Info, int>::iterator it;
for(it = m.begin(); it != m.end(); it++)
{
cout << it->first.name << endl;
}
return 0;
C++中虚函数表存储在什么位置?
C++中虚函数表位于只读数据段(.rodata),也就是C++内存模型中的常量区;而虚函数则位于代码段(.text),也就是C++内存模型中的代码区。
C++中能不能在 main
前执行代码?
答:可以。
- 全局类变量的构造都在main之前。可以通过全局变量来在
main
前面执行代码。 static
标识符标记的全局变量在程序初始化阶段,先于main
执行。
//全局static变量的初始化在程序初始阶段,先于main函数的执行,所以可以利用这一点。在leetcode里经常见到利用一点,在main之前关闭cin与stdin的同步来“加快”速度的黑科技:
static int _ = []{
cin.sync_with_stdio(false);
return 0;
}();
//_attribute((constructor))是gcc扩展,标记这个函数应当在main函数之前执行。同样有一个__attribute((destructor)),标记函数应当在程序结束之前(main结束之后,或者调用了exit后)执行;
其实想一想 main
无非就是个入口点,只不过是更改入口点而已。
typedef 关键字wiki
在C和C++编程语言中,typedef是一个关键字。它用来对一个数据类型取一个别名,目的是为了使源代码更易于阅读和理解。它通常用于简化声明复杂的类型组成的结构 ,但它也常常在各种长度的整数数据类型中看到,例如size_t和time_t。
typedef的语法是 :
typedef typedeclaration;
和结构体一起使用
typedef struct Node Node;
struct Node {
int data;
Node *nextptr;
};
和指针一起使用
typedef int *intptr;
intptr cliff, allen; // both cliff and allen are int* type
intptr cliff2, *allen2; // cliff2 is int* type, but allen2 is int** type
// same as: intptr cliff2;
// intptr *allen2;
和结构体指针一起使用
typedef struct Node Node;
struct Node {
int data;
Node *nextptr;
};
和函数指针一起使用
先来看这段没有使用typedef的代码:
int do_math(float arg1, int arg2) {
return arg2;
}
int call_a_func(int (*call_this)(float, int)) {
int output = call_this(5.5, 7);
return output;
}
int final_result = call_a_func(&do_math);
注意:这里的call_this是指向参数类型为(float, int) ,返回值是int类型的函数指针,另外注意函数指针的用法,
使用typedef后这段代码可以改写为:
typedef int (*MathFunc)(float, int);
int do_math(float arg1, int arg2) {
return arg2;
}
int call_a_func(MathFunc call_this) {
int output = call_this(5.5, 7);
return output;
}
int final_result = call_a_func(&do_math);
前加一个typedef关键字,这样就定义一个名为MathFunc的函数指针类型,而不是一个MathFunc变量。
指针
什么是指针?
我们指知道:C语言中的数组是指 一类 类型,数组具体区分为 int 类型数组,double类型数组,char数组 等等。同样指针 这个概念也泛指 一类 数据类型,int指针类型,double指针类型,char指针类型等等。
通常,我们用int类型保存一些整型的数据,如 int num = 97 , 我们也会用char来存储字符: char ch = ‘a’。
我们也必须知道:任何程序数据载入内存后,在内存都有他们的地址,这就是指针。而为了保存一个数据在内存中的地址,我们就需要指针变量。
因此:指针是程序数据在内存中的地址,而指针变量是用来保存这些地址的变量。
指针本身也是一种数据类型
为什么需要指针?
指针解决了一些编程中基本的问题。
第一,指针的使用使得不同区域的代码可以轻易的共享内存数据。当然你也可以通过数据的复制达到相同的效果,但是这样往往效率不太好,因为诸如结构体等大型数据,占用的字节数多,复制很消耗性能。但使用指针就可以很好的避免这个问题,因为任何类型的指针占用的字节数都是一样的(根据平台不同,有4字节或者8字节或者其他可能)。
第二,指针使得一些复杂的链接性的数据结构的构建成为可能,比如链表,链式二叉树等等。
第三,有些操作必须使用指针。如操作申请的堆内存。还有:C语言中的一切函数调用中,值传递都是“按值传递(pass by value)”的,如果我们要在函数中修改被传递过来的对象,就必须通过这个对象的指针来完成。
什么是指针变量?
指针变量是用来存放指针(地址)的变量。
int c = 76;
int *pointer; //此处int是指针变量的基类型,基类型就是指针变量指向的变量的类型
pointer = &c;
//将变量c的地址赋值给指针变量pointer
//赋值后,称指针变量pointer指向了变量c
指针运算符 *
取址运算符&
指针变量也是变量,是变量就有地址
关于空指针
void*类型指针
由于void是空类型,因此void类型的指针只保存了指针的值,而丢失了类型信息,我们不知道他指向的数据是什么类型的,只指定这个数据在内存中的起始地址,如果想要完整的提取指向的数据,程序员就必须对这个指针做出正确的类型转换,然后再解指针。因为,编译器不允许直接对void类型的指针做解指针操作。
函数指针的几个疑惑
问题:c语言中, 函数名也称为函数的指针,那函数名是否也占内存空间?
首先你上面的话是错误的,函数名是一段指令的入口地址,它是地址常量,不占用内存空间,只是在编译阶段存在于编译器的符号表中,例如函数的入口地址是0x123456,在翻译成机器指令以后,函数名是不存在的其在本质上对应汇编上的jump指令,在执行函数的时候,跳转到0x123456,这个函数名的本质就是这个地址。
c语言中其他变量的原理也都是类似的
#include <iostream>
using namespace std;
typedef int (*funcptr)(int, int);
int add(int a,int b){
return a+b;
}
int test(funcptr ptr,int x, int y){
cout<<"this is a test";
return ptr(x, y);
}
int main(){
cout<<test(add,2,3)<<endl;
cout<<test(&add,3,4)<<endl;
cout<<&main;
return 0;
}
1)其实,MyFun的函数名与FunP函数指针都是一样的,即都是函数指针。MyFun函数名是一个函数指针常量,而FunP是一个函数数指针变量,这是它们的关系。
2)但函数名调用如果都得如(*MyFun)(10)这样,那书写与读起来都是不方便和不习惯的。所以C语言的设计者们才会设计成又可允许MyFun(10)这种形式地调用(这样方便多了并与数学中的函数形式一样,不是吗?)。
3)为统一起见,FunP函数指针变量也可以FunP(10)的形式来调用。
4)赋值时,即可FunP = &MyFun形式,也可FunP = MyFun。
C++的左值与右值
基本概念
左值与右值的概念在很多地方比较模糊,但其对我们对C++的理解很重要。比如我们看github上的源码的时候会看到std::move等用法,在查找其含义之后得知它功能是将左值转成右值引用,若是我们不理解左值与右值,还是无法知道它到底有什么用。
我们还会经常在编译错误和警告信息中看到左值右值概念的出现。
左值与右值的简单定义
lvalue(locator value), 即左值,代表一个在内存中占有确定位置的对象,换句话说就是有一个地址。
rvalue:一个表达式要么是lvalue,要么是rvalue。所以,不是lvalue的表达式就是rvalue。
左值是指表达式结束后依然存在的持久对象,右值是指表达式结束时就不再存在的临时对象。一个区分左值与右值的便捷方法是:看能不能对表达式取地址,如果能,则为左值,否则为右值。
C++ 11中用&表示左值引用,用&&表示右值引用
// Big Block
// https://www.nowcoder.com/discuss/418915
#include <string>
#include <iostream>
using namespace std;
int main() {
{
std::string s = "1234";
cout << s << endl; // "1234"
}
{
std::string s = "1234";
std::move(s);
cout << s << endl; // "1234"
}
{
std::string s = "1234";
const auto& s1 = std::move(s);
cout << s << ' ' << s1 << endl; // "1234 1234"
}
{
std::string s = "1234";
auto&& s1 = std::move(s);
cout << s << ' ' << s1 << endl; // "1234 1234"
}
{
std::string s = "1234";
auto s1 = std::move(s);
cout << s << ' ' << s1 << endl; // " 1234"
}
return 0;
}
C++迭代器
#include <vector>
#include <iostream>
using namespace std;
int main(){
vector<int> v1;
for(int i=0;i<100;i++){
if(i%3 ==0)
v1.push_back(i);
}
vector<int>::iterator it;
for(vector<int>::iterator it=v1.begin(); it != v1.end();it++){
*it += 2;
cout<<*it<<endl;
}
return 0;
}
注意迭代器的用法
::不要丢掉,否则语法错误;迭代器的本质是指针,指针在使用之前一定要赋值
小知识
- c语言print()函数的参数
- %d ——–dicimal(base 10)
- %x ——–hexadecimat(base 16)
- %o ——–octal(base 8)