C++简单编译
auto关键字
auto
关键字用于自动类型推导。它允许编译器自动推断变量的类型,使得代码更加简洁和易于编写,尤其是在处理复杂类型或模板编程时。使用auto
可以避免编写冗长的类型声明,同时减少由于类型不匹配导致的编译错误
auto x = 5; // x 的类型是 int auto y = 3.14; // y 的类型是 double auto z = 'a'; // z 的类型是 char // 指针 int* p = &x; auto p1 = &x; // p1 的类型是 int* // 引用 auto& r = x; // r 是 x 的引用,类型为 int& // 复杂的类型推导 std::map<int, std::string> myMap = {{1, "one"}, {2, "two"}}; auto iter = myMap.begin(); // iter 的类型是 std::map<int, std::string>::iterator
注意事项:
初始化:使用
auto
声明的变量必须被初始化,因为编译器需要依据初始化表达式来推导类型。指针和引用:可以通过在
auto
后面添加*
或&
来声明指针或引用类型的变量。- 范围for循环:
auto
在范围for循环中非常有用,因为它可以自动处理迭代器的类型。std::vector<int> v = {1, 2, 3, 4, 5}; for (auto i : v) { // i 的类型是 int,并且是 v 中元素的拷贝 } for (auto& i : v) { // i 是对 v 中元素的引用,可以修改 v 中的元素 }
- 模版编程:在模板编程中,
auto
尤其有用,因为它可以帮助避免编写复杂的类型声明
using namespace std; int add(int *a,int *b) { ++ *a; ++ *b; return *a + *b; } int main() { int i =10,j =9; cout << add(&i,&j) <<endl; cout << i <<"," << j <<endl; }
引用 &
- 定义引用:
int a = 10 ;
int & b = a ; // b是a的引用b
和a
指向内存中的同一个位置。对b
的任何非const修改都会反映到a
上。引用的限制:
引用在定义时必须被初始化。
引用一旦被初始化,就不能再改变为引用另一个对象。
引用不能为空函数参数和返回值中的引用
引用常用于函数参数和返回值中,以实现高效的数据传递和返回,
------ 函数参数:通过引用传递参数可以避免拷贝大型对象,提高程序效率。
void swap(int& a, int& b) { int temp = a; a = b; b = temp; }
------ 函数返回值:通过引用返回可以避免拷贝,但需要注意避免返回局部变量的引用,因为这会导致悬垂引用
int& findMax(int& a, int& b) { return (a > b) ? a : b; }
常量引用:
常量引用允许我们引用一个常量对象或非常量对象,但不允许我们通过这个引用来修改对象的值
const int c = 30; const int& d = c; // 正确:d是c的常量引用 int& e = c; // 错误:不能通过非常量引用来引用常量对象
简述指针与引用的关系?
相同点:
间接访问:两者都允许通过它们来访问和操作另一个变量的值,而不是直接访问该变量的内存地址。
函数参数:在函数中,使用指针或引用作为参数可以避免复制大型对象,提高效率,并且允许函数修改传入的变量的值。
返回非局部变量的值:都可以用来从函数中返回非局部变量的地址或引用,从而允许在函数外部访问和修改这些变量的值。
不同点:
(1)语法:
- 指针需要解引用(使用
*
操作符)来访问其指向的值。- 引用则像是一个变量的别名,不需要特殊的操作符来访问其值。
(2)空值:
- 指针可以被设置为
nullptr
(或NULL
,在C++11之前),表示它不指向任何对象。- 引用必须在声明时被初始化,且一旦被绑定到一个对象,就不能再被改变为引用另一个对象,即引用不能为空。
(3) 赋值
- 指针可以被重新赋值以指向另一个对象。
- 引用一旦绑定到一个对象,就不能再指向另一个对象。
(3)类型安全:
- 指针可以进行算术运算(如递增或递减),这可能导致它指向无效的内存区域,降低类型安全性。
- 引用不支持算术运算,这增加了类型安全性,但同时限制了灵活性。
(4)大小
- 指针本身是一个变量,它存储的是另一个变量的内存地址,因此指针有大小(通常是机器字长)。
- 引用则是一个别名,不占用额外的存储空间(不存储地址,只是别名),但从实现的角度来看,编译器可能会以指针的形式来处理引用。
(5)使用场景:
- 指针因其灵活性(如动态内存分配、数组操作、链表等数据结构)而被广泛使用。
- 引用则因其安全性和简洁性(如函数参数传递、返回值等)而在C++中被推荐用于需要引用语义的场合。
内联函数 inline
定义:
inline 是一种建议 不是命令,是否真的展开取决于编译器
声明:
内联函数的 inline 关键字只放在头文件不放在源文件中
虽然可以在函数声明前添加
inline
关键字,但这种方式通常是无效的,因为编译器在编译时只关注函数定义处的inline
关键字。因此,通常将inline
关键字与函数定义体放在一起特点:
- 编译时展开:内联函数在编译时会被直接插入到调用该函数的地方,而不是像普通函数那样生成函数调用的指令。这消除了函数调用的开销,包括参数传递、栈帧创建和销毁等。
#include <iostream> // 声明内联函数 inline int max(int a, int b) { return (a > b) ? a : b; } int main() { int x = 5, y = 10, z; z = max(x, y); // 这里调用max函数,编译器可能会将其内联展开 std::cout << "The maximum is " << z << std::endl; return 0; }
- 适用于简单函数:内联函数通常适用于函数体较小、执行时间较短的函数。如果函数体过大或包含复杂的控制结构(如循环、递归等),编译器可能不会将其内联,因为这可能导致代码膨胀和性能下降。
- 代码膨胀:由于内联函数的代码会被复制到每一个调用处,因此如果内联函数被频繁调用或代码较大,可能会导致程序体积增大。
- 通常放在头文件中:为了避免链接错误,内联函数的定义通常放在头文件中,以便在需要调用的地方进行内联展开。
例如:
inline void fm(int &n) { ++n; } int main() { int i =10; int j =20; fm(i); cout<< i <<endl; }
inline void fm(const int &n) { n; } int main() { int i =10; fm(10); cout<< i <<endl; }
结果:11
10
默认形参值
默认形参值只放在声明不放在定义
注意点:
默认值的位置:一旦你为某个参数指定了默认值,那么该参数之后的所有参数都必须有默认值。
int add ( int a = 0 , int b = 0 ) { cout << "a = " << a <<endl; cout << "b = " << b <<endl; return a + b ; } int main() { cout << add() << endl;//可以传参一可以不传参,如果不传参则都为0 //cout << add(10,20) << endl; return 0; } 结果:a = 0 ,b = 0 ,0 a = 10,b = 20 , 30
函数声明与定义:通常,在函数声明中指定默认值,并在函数定义中省略这些值。但是,你也可以在函数定义中直接指定默认值,只要确保在调用函数之前,编译器已经看到了这些默认值(即,函数声明或定义在调用点之前)。
头文件与源文件:如果函数声明在头文件中,而定义在源文件中,那么默认值应该在头文件的函数声明中指定。
重载与默认参数:具有默认参数的函数可以被重载。编译器会根据提供的参数数量和类型来选择最合适的函数版本。
函数指针与默认参数:当通过函数指针调用函数时,默认参数不会生效。你需要显式地提供所有参数。
构造函数与默认参数:类的构造函数也可以有默认参数,这对于创建具有可选成员变量的对象非常有用。
函数重载
函数重载的基本规则
- 函数名相同:重载的函数必须具有相同的名称。
- 参数列表不同:函数的参数列表必须不同。这可以是参数的类型不同、参数的个数不同,或者参数的顺序不同(尽管在实际应用中,改变参数顺序通常不是一个好的设计选择,因为它可能导致代码难以理解和维护)。
- 返回类型可以相同也可以不同:返回类型不是决定函数是否重载的因素。但是,习惯上,如果函数的功能相似,那么最好保持返回类型也一致,以增强代码的可读性。
- 仅返回类型不同不足以构成重载:如果两个函数只是返回类型不同,但参数列表完全相同,则编译器会报错,因为这不是有效的重载。
#include <iostream> using namespace std; //函数重载示例 同名但不同参 void print(int i) { cout << "Printing int: " << i << endl; } void print(double f) { cout << "Printing float: " << f << endl; } void print(const char* c) { cout << "Printing character: " << c << endl; } int main() { print(7); //调用 print(int) print(7.7); //调用 print(double) print("Hello"); //调用 print(const char*) return 0; }
const
const
关键字是一个非常重要的特性,它用于指定变量的值在初始化之后不能被修改。const
可以应用于变量、指针、函数参数、返回值等多种情况,以提高代码的可读性和安全性
- 修饰变量
当const
用于修饰变量时,表示该变量的值是一个常量,在程序运行过程中不能被修改修饰指针
(1)指向常量的指针(指针指向的值不能被修改)const int* ptr = &x; // ptr是一个指向整数的指针,但这个整数是const的,即*ptr的值不能被修改
(2)常量指针(指针指向的值不能被修改,但指向的值可以改变)
int* const ptr = &x; // ptr是一个const指针,指向x,但ptr本身的值(即它所指向的地址)不能被修改
(3)同时修饰指针和指向的值:
const int* const ptr = &x; // ptr是一个const指针,指向一个const整数,即ptr的值和*ptr的值都不能被修改
修饰函数参数
在函数定义中,使用const
修饰参数可以告诉调用者这个参数在函数内部不会被修改,这有助于编译器进行优化,并增加了代码的清晰度
void display(const int& x) { // 在这里,你不能修改x的值 cout << x << endl; }
修饰返回值
const
也可以用于修饰函数的返回值,但这种情况比较少见。当const
用于修饰返回值时,它表示返回的对象是一个常量,调用者不能通过这个返回值去修改对象的状态
const int getValue() { // 返回一个常量int return 42; } // 注意,对于自定义类型(如类),返回const对象通常是为了表示这个对象是只读的,不应该被修改 const MyClass getMyClassInstance() { // ... }
然而,对于自定义类型(如类)的返回值,如果返回的是对象本身而不是引用或指针,那么
const
修饰符可能并不那么有用,因为返回值是一个临时对象,它本身就不能被赋值给非const
的变量(这会导致拷贝)。但如果是返回引用或指针,那么const
修饰符就显得非常重要了成员函数
在类中,const
成员函数表示该函数不会修改类的任何成员变量(除了mutable
修饰的成员变量)。这有助于保证对象的状态在调用该函数后不会发生变化class MyClass { public: void myFunction() const { // 这里不能修改类的成员变量 } };
不能实现
常引用
void(const int &a)
常引用通过在类型前添加
const
关键字来声明。常引用只能用于读取它所引用的数据,而不能用于修改。这使得常引用在以下场景中特别有用:
- 保护数据:当你想要通过函数参数传递一个对象,但又不希望这个函数修改这个对象时,可以使用常引用。
- 提高效率:对于大型对象或容器,通过引用传递可以避免不必要的复制,同时
const
修饰符又保证了数据的安全性。- 函数返回值:返回对内部成员或局部静态变量的常引用可以避免拷贝,并保护这些变量不被外部修改。
非常引用(或者普通引用)
void (int &a)
没有
const
修饰的引用就是非常引用或普通引用。它允许通过引用修改它所引用的数据。非常引用在需要修改数据的函数中非常有用,因为它们提供了对原始数据的直接访问
#include <iostream> using namespace std; void printValue(const int& x) { // 使用常引用接收参数,保证不会修改x cout << x << endl; } void modifyValue(int& x) { // 使用非常引用接收参数,可以修改x x = 10; } int main() { int a = 5; printValue(a); // 输出5 modifyValue(a); // 修改a的值为10 printValue(a); // 现在输出10 return 0; }