由于之前学过C语言、java语言,所以这一版笔记不会把所有内容都记录下来,仅记录学习过程中不太清楚的,或者忘记的。比较基础的就不再进一步记录了。
1.关于字符串的补充
在之前的C语言学习中,已经详细的学习了C的各类数据类型,无论是整型还是浮点型,以及对应的作用范围,这里C++相较于大部分C的数据类型的声明,基本都还可以沿用,但是也多了一些优化。
类似于字符串的命名,我们既可以使用C风格下面的命名方式:
char str1[]="hello world";
也可以使用C++中新增的风格命名方式:
(java中也有的一个关键字 string)
string str1="hello world";
当然这里如果想使用string这个关键字的话,一些版本比较早的IDE会需要引用string 的头部文件。
#include <string>
补充一点知识:bool类型和char类型都是只占用一个字节
2.数据的输入和输出
作用类似于C语言的scanf、print关键字,从键盘中获取数据。关键字是:cin、cout
类似于java中的是
system.out.println();
Scanner scanner = new Scanner(System.in);
语法:cin>>变量、cout<<变量
注意其中的尖括号的朝向。
简单举几个例子,一个整型一个字符串型
int main(){
/*
数据的输入
整型
*/
int a = 0;
cout<<"请输入一个数字:"<<endl;
cin >> a;
cout<<"您输入的数字为"<<a<<endl;
/*
字符串型
*/
string str1;
cout<<"请输入你的名字:"<<endl;
cin >> str1;
cout<<"你输入的值为"<<str1<<endl;
}
回车输入。
另外需要特别注意的一个类型是bool型,如果键盘输入的bool型真为1,假为0,即便输入的是100最后结果也是1。
非0均为真
bool flag;
cout<<"请输入bool值:"<<endl;
cin >> flag;
cout<<"你输入的值为"<<flag<<endl;
3.取模运算
第一点:取模运算,两个相除的除数不可以为0,这个是符合正常的数学逻辑。在linux通过gcc编译时通过了,但是实际运行后是得不到结果的,会报异常:
int main(){
/*
取模运算
两个相除的除数不可以为0,所以做不了取模运算
*/
int a1=10;
int b1=0;
cout<<a1<<"与"<<b1<<"取余结果为:"<<a1 % b1<<endl;
}
编译后运行结果为:
第二点:取模运算,两个小数是不可以做取模运算的。这里在linux通过gcc编译时都会报错。
int main(){
/*
两个小数是不可以做取模运算的
*/
double d1=3.15;
double d2=1.1;
cout<<a1<<"与"<<b1<<"取余结果为:"<<d1 % d2<<endl;
}
编译报错为:
4.C++中,函数的形式参数列表中的形式参数是可以有默认值的。
以如下为例:
int func (int a,int b=1,int c=1){
return a+b+c;
}
int main(){
int num1=35;
int num2=5;
int num3 =15;
int sum1=func(num1);
int sum2=func(num1,num2,num3);
cout<<sum1<<endl;
cout<<sum2<<endl;
}
定义的函数func中为其形参b和c 设定了默认值,如果调用该函数时不传入指定值,则使用默认值,如果传入的指定值,那就使用传入的值来计算。
运行结果:
需要注意的问题1:
需要注意的是:如果形式参数的从某个开始有了默认参数,那么从这个位置往后都必须有默认值。
举个例子,在创建一个func2函数,我们给第一个参数a一个默认值,b,c不管。
int func2(int a=3,int b,int c);
int main(){
int num1=35;
int num2=5;
int num3 =15;
int sum3 = func2(num1,num2,num3);
cout<< sum3<<endl;
}
编译会报错:
所以规定里,如果形式参数给默认值,则从这个参数开始后续的必须都要给默认值。
需要注意的问题2:
如果函数声明中有默认参数,函数实现就不能有默认参数。
举例来说:
int func(int a,int b);
int main(){
cout<< func(1,2)<<endl;
return 0;
}
int func(int a,int b){
return a+b;
}
通常来说,函数的声明和函数体的实现是分别放在头文件和.cpp文件中的,这里输出结果为3,没问题。
但是如果这样改一下,同时给函数声明和函数体加上默认值:
int func(int a =2,int b=4);
int main(){
cout<< func(1,2)<<endl;
return 0;
}
int func(int a=2,int b=4){
return a+b;
}
编译就会报错:
修改的时候:需要把声明的默认值去掉,或者把函数实现的默认值去掉,保留一个即可。
int func(int a ,int b);
int main(){
cout<< func(1,2)<<endl;
return 0;
}
int func(int a=2,int b=4){
return a+b;
}
5.函数占位参数
C++中函数的形式参数列表可以有占位参数,用来做占位,调用函数时,必须填补该位置。
下面是一个正常的函数:
void func(int a){
cout<<"this is a func"<<endl;
}
int main(){
func(100);
}
这样是正常编译,也可以正常运行出理想的结果。
我们修改一下func,改为:
void func(int a,int){
cout<<"this is a func"<<endl;
}
int main(){
func(100);
}
再次编译,看一下对不对:
会发现这时候编译错误了,显示缺少了一个参数,由此我们可以得到结果:占位的参数也必须传递。
void func(int a,int){
cout<<"this is a func"<<endl;
}
int main(){
func(100,200);
}
6.函数重载
函数名可以相通,参数不同,以提高复用性。
函数重载的满足条件:
1.同一个作用域下。
2.函数名相同。
3.函数参数不同(类型不同,或者个数不同,或者顺序不同)
注意:函数的返回值不可以作为函数重载的条件。
举个简单的例子
/*
函数重载
*/
//函数名相同,一个int类型参数
int func(int a);
//两个int类型参数,一个占位
int func(int a,int );
//一个double类型占位参数。
int func(double);
int main(){
func(1);
func(10,20);
func(3.14);
return 0;
}
int func(int a){
cout<<"this is func Overload 1"<<endl;
return 0;
}
int func(int a,int){
cout<<"this is func Overload 2"<<endl;
return 0;
}
int func(double){
cout<<"this is func Overload 3"<<endl;
return 0;
}
输出结果:
这样我们可以发现给定的参数不同,其会运行不同的函数体。
需要注意的事项:
有以下两个需要注意的地方:
引用作为重载条件,函数重载碰到函数的默认参数。
1.引用作为重载条件:
/*
重载注意事项
1.引用作为重载条件,函数重载碰到默认参数。
*/
void overload(int a){
cout<<"普通重载函数"<<endl;
}
void overload(int a,int &b){
cout<<"包含引用参数的重载函数"<<endl;
}
void overload(int a,const int &b){
cout<<"包含静态修饰的引用参数的重载函数"<<endl;
}
int main(){
int a=3,b=4;
const int c=5;
overload(a);
overload(a,b);
overload(a,c);
}
输出结果:
可以看到引用和const修饰的引用,是可以区分出来不同的重载函数的。在调用的时候给定对应的值即可。
碰到默认参数的问题:
/*
函数重载碰到默认参数
*/
void overload1(int a){
cout<<"重载函数1 a="<<a<<endl;
}
void overload1(int a,int b=15){
cout<<"重载函数2 a="<<a<<endl;
}
int main(){
int num1=5;
overload1(num1);
overload1(num1);
return 0;
}
这里我们编译,gcc就会给我报错了:
原因是存在函数重载的冲突问题。C++不允许定义两个参数相同或仅通过默认参数不同的重载函数。这是因为调用函数时会产生歧义,编译器无法确定应该调用哪个函数。
那么由此就可以知道,不能以默认参数来区分重载函数。
7.new操作符
C++中利用new 操作符在堆区开辟数据,有程序员手动开辟,手动释放,释放利用操作符delete。
基本语法示例:
/*
引用
*/
int * func(){
int * a=new int(10);
return a;
}
int main(){
int *function = func();
cout<< *function <<endl;
return 0;
}
编译输出结果:
上述示例我们用在堆区创建整型数据,new返回的是数据类型的指针,输出的结果为开辟空间的字节数,并且上述代码并没有释放,不释放这块空间一直存在,所以我们需要加上释放代码。
/*
引用
*/
int * func(){
int * a=new int(10);
return a;
}
int main(){
int *function = func();
cout<< *function <<endl;
delete function;
cout<< *function <<endl;
return 0;
}
删除掉之后,我们在此输出一下,可以看到结果已经不是申请的空间大小了。这里就表明空间被释放了,这里访问就是非法访问了。
那如果是创建数组呢?应该如何创建。
void func2(){
int * arr=new int[10];
for(int i=0;i<10;i++){
arr[i]=i+1;
}
for(int i=0;i<10;i++){
cout<<arr[i]<<" ";
if(arr[i]==10)
cout<<endl;
}
delete[] arr;
}
int main(){
func2();
return 0;
}
尤其需要注意的是,在之前C语言中关于数组的笔记中,我们知道数组名只是指向第一个地址的位置,这里如果释放数组空间的时候如果仅仅这样来写:
delete arr;
就可能只释放了首地址的空间,后续的空间其实并没有释放,C++并未定义这个行为,所以为了保证正确性,数组释放时一定要记得带上[];
delete[] arr;
8.引用
引用也算是C++中一个比较重要的点,作用就是给变量起别名。
/**
引用
*/
void func3(){
int a =10;
int &b=a;
}
这里有一个变量a指向了一个4字节的空间内容为10;b也指向了这个4字节的空间。如果我要修改了b那么a的值是多少?
void func3(){
int a =10;
int &b=a;
cout<<a<<endl;
b=100;
cout<<a<<endl;
}
输出结果是:
那么我们可以发现这里修改了b的值后,变量a的值也被修改了,也就是说,无论是b还是a只是变量名不同,但是指向的内容是相通的,值要修改了这片空间的内容,a和b都会变成修改后的内容。
引用的用途主要集中在:函数参数传递以及函数返回值优化。
1.引用常用于函数参数传递,特别是在传递大型对象或复杂结构时,引用可以避免不必要的拷贝,从而提高性能。
void increment(int& a) {
a++;
}
int main() {
int x = 5;
increment(x);
std::cout << "x = " << x << std::endl; // 输出 x = 6
return 0;
}
2.函数可以返回引用,这在返回大型对象时非常有用,可以避免不必要的拷贝
int& getReference(int& a) {
return a;
}
int main() {
int x = 5;
int& ref = getReference(x);
ref = 10;
std::cout << "x = " << x << std::endl; // 输出 x = 10
return 0;
}
需要注意的是:
1.引用必须在定义时初始化,并且不能更改指向的对象。
2.通过引用访问变量不需要解引用操作符(*)。
3.引用不能为 NULL,它必须始终引用一个有效的对象。
另外需要区分和指针的区别:
指针可以在任何时候初始化,并且可以在程序运行过程中更改指向的对象。通过指针访问变量需要解引用操作符 *
。指针可以为 NULL,表示它不指向任何对象。
示例:
#include <iostream>
void modifyValue(int& ref) {
ref = 20; // 修改引用所指向的变量
}
int main() {
int x = 10;
std::cout << "Before: " << x << std::endl; // 输出 10
//实际这里等同于,给x起了个别名ref,然后通过ref去修改这个值同时作用到了变量x。
modifyValue(x); // 通过引用传递
std::cout << "After: " << x << std::endl; // 输出 20
return 0;
}
在这个示例中,函数 modifyValue 接受一个整型引用,并将其值修改为 20。当 x 被传递给 modifyValue 时,实际上传递的是 x 本身,因此函数内的修改直接影响到 x。
错误示例,假设我现在想要使用引用传参数,同时要返回值引用,写了如下一个函数:
int& func4(int& a, int& b){
return a+b;
}
int main(){
int num1=15;
int num2=16;
int &sum=func4(num1,num2);
return 0;
}
编译就会报错了
但是这个例子是有问题的,问题在于a + b 是一个临时值(临时变量),它在表达式求值结束后就不存在了,不能返回对临时值的引用。如果我们实在想使用引用传递以及返回值引用来实现两个数相加,应该怎么做呢?
void func4(int& a, int& b, int& result) {
result = a + b; // 将结果存储在 result 引用中
}
int main() {
int num1 = 15;
int num2 = 16;
int sum;
func4(num1, num2, sum); // 将计算结果存储在 sum 中
std::cout << "Sum: " << sum << std::endl; // 输出结果
return 0;
}
可以给一个变量result,用它来存储计算结果。这样引用就不会指向临时变量,就不会报错了。