#include <iostream> using namespace std; int main(int argc,const char* argv[]) { cout << "Hello World!" << endl; return 0; }
文件扩展名:
cpp、C、cxx
头文件:
C++语言的标准库文件,文件名的末尾不带.h,iostream用于标准输入输出头文件,C语言的相关头文件还可以继续使用。
为了统一命名风格,C++为C语言重定义了不带.h标准库头文件,例如:stdio.h重定义了cstdio。
自定义的头文件,还可以继续以.h结尾。
编译器:
g++,相关参数的使用方法与gcc一样。
输入、输出:
cout、cin是用于输入、输出的标准库类对象。
cout << 要输出的数据 << endl,多个数据用<<隔开。
cin >> 变量名,多个数据使用>>隔开。
cout和cin可以自动识别数据类型,但输入输出复杂格式的数据时,没有printf和scanf好用。
printf和scanf还可以继续使用,但需要包含相关的头文件。
名字空间:
为了避免命名冲突,C++中引入了一项命名空间的管理技术 名字空间,后续再讲解。
注意:C++基本上完全兼容C语言的所有内容。
练习:
输入n个整数,计算出它们的最大值、最小值、平均值。
#include <iostream> using namespace std; int main(int argc,const char* argv[]) { int n; cin >> n; int val, max=0x80000000, min=0x7fffffff, sum=0; for(int i=0; i<n; i++) { cin >> val; sum += val; if(val > max) max = val; if(val < min) min = val; } cout << max << " " << min << " " << sum / n << endl; return 0; }
二、基本数据类型的不同
bool类型:
在C++中,bool就是一种真正的基本数据类型,不需要包含stdbool.h头文件了,bool、true、false是C++中的关键字。
可以给bool类型的变量赋值整数,但它会自动转换成0或1。
#include <iostream> using namespace std; int main(int argc,const char* argv[]) { bool flag; cout << sizeof(bool) << " " << sizeof(true) << " " << sizeof(false) << endl; flag = 4; cout << flag << endl; // 输出的1,会把整数自动转换成0|1。 return 0; }
void类型的指针:
在C语言void类型的指针可以与其它类型的指针自动转换(通用指针),而在C++中:
其它类型指针 可以自动转换成 void*,之所以保留是因为C标准库、操作系统、第三方库中有大量的函数的参数使用了void*作为参数,如果该功能不保留,这类函数就无法再正常调用。
void* 不能再自动转换成 其它类型指针,为了安全C++语言对类型检查比C语言要严格很多。
#include <iostream> #include <cstdlib> using namespace std; int main(int argc,const char* argv[]) { int* p = (int*)malloc(40); return 0; }
字符串:
在C语言中使用char类型的数组或char*指针指向的内存来存储字符串,使用string.h中的函数操作字符串,但在C++中使用string类型字符串变量,使用相关的运算符操作字符串。
#include <iostream> using namespace std; int main() { // 定义字符串对象 string str; // 输入字符串,不用关心存储空间是否够用,会自动扩展 cin >> str; // 输出字符串 cout << str << endl; // 给字符串赋值,strcpy str = "hello"; // 追加字符串,strcat str += "world"; // 计算字符串长度,strlen cout << str.size() << endl; // 比较字符串,== != > < >= <= ,strcmp cout << (str == "xixi") << endl; }
注意:string类型的底层,依然是使用char类型的指针、数组实现的,并不是一种全新的类型,而是对char字符串的封装。
三、结构、联合、枚举的区别
结构、联合的区别:
1、在C++中定义结构、联合对象时,struct、union关键字可以省略(也就不需要使用typedef进行类型重定义)。
2、在C++中结构、联合的内部,可以有成员函数,使用结构、联合对象加.或->调用,成员函数的内部可以直接使用成员变量,成员函数可以自动区别对象的成员变量。
3、在C++中结构、联合中可以对成员变量、成员函数进行访问权限的管理:
private 私有的成员
public 公开的成员
protected 受保护的成员
4、在C++中创建、销毁结构、联合对象时,会自动调用构造函数(以结构名命名)、析构函数(~结构名命名)。
#include <iostream> using namespace std; struct Student { int id; char name[20]; short age; void show(void) { cout << id << " " << name << " " << age << endl; } Student(void) { cout << "我是构造函数" << endl; } ~Student(void) { cout << "我是析构函数" << endl; } }; union Data { char ch; int num; }; int main(int argc,const char* argv[]) { /* Student stu1 = {10010,"hehe",28}; Student stu2 = {10011,"xixi",30}; stu1.show(); stu2.show(); */ Student stu; Data d; return 0; }
枚举:
1、定义枚举变量时,enum关键字可以省略。
2、C++中的枚举不再是int类型模拟的,整数不能给枚举变量赋值。
#include <iostream> using namespace std; enum DirectionKey { Up,Down,Right,Left }; int main(int argc,const char* argv[]) { DirectionKey key; // key = 1234; key = Down; cout << key << endl; return 0; }
四、函数的区别
1、函数重载:
1、C++中的函数重名,我们把这项技术叫函数重载。
2、重载的函数,参数列表必须不同,即参数的个、类型不同。
3、调用函数时会根据实参的数据类型自动选择调用那个函数,如果与实参相符的函数没有定义,则可以对实参进行自动类型转换调用相关函数,如果实参进行自动类型转换后有多个选则会导致调用冲突。
#include <iostream> using namespace std; void func(long num) { cout << "func long类型参数" << endl; } void func(short num) { cout << "func short类型参数" << endl; } int main(int argc,const char* argv[]) { func(1234); return 0; }
4、如果函数的参数是指针或引用,指针变量是否使用const修饰会影响函数重载。
void func(int* p) { cout << "func int* p" << endl; } void func(const int* p) { cout << "func const int* p" << endl; } int main(int argc,const char* argv[]) { int num1; func(&num1); // void func(int* p) const int num2; func(&num2); // void func(const int* p) return 0; }
构成函数重载的条件:
1、同一个作用域
2、函数名相同
3、参数列表不同(参数的个数、类型,指针、引用是否加const)
函数重载的原理:
C++中的函数重载并不是真正的重名,而是在编译时编译器会把函数的参数列表信息追加到函数名的末尾,也就是在编译时函数名经历的换名的过程,在函数调用时,编译器会把实参的类型信息追加到函数名的末尾,这样就知道该调用哪个函数了。
// _Z4funcPi: void func(int* p) { cout << "func int* p" << endl; } // _Z4funcPKi: void func(const int* p) { cout << "func const int* p" << endl; } // _Z4funcii: void func(int num,int num1) { cout << "func const int int" << endl; }
C++中如何使用C的库文件:
面临的问题:
1、把函数声明头文件导入后,默认情况下g++会按照C++的语法声明函数,会把C头文件中的函数声明进行换名。
2、g++在编译调用函数的语句时,会先尝试调用换名的函数,此时发现已经声明过,所以编译器生成的换名的函数调用指令,而C的库文件中的函数没有换名,所以就会调用失败,也就链接失败。
解决方法:
1、使用extern "C" 包含一下C函数声明,这个语句的功能就是告诉编译器按照C语言的处理方式编译函数声明,也就是不要对函数进行换名。
2、g++在编译调用函数的语句时,会先尝试调用换名的函数,此时会发现没有该版本的函数声明,然后再尝试调用未换名的函数,这样编译器生成就是未换名的函数调用指令,就能成功调用C的库文件中的函数。
注意:函数重载不是为实现通用代码,只为了提高代码的可读性(降低取名难度)。
练习:使用函数重载技术实现一个基本类型通用的冒泡排序函数。
signed char signed short signed int signed long unsigned char unsigned short unsigned int unsigned long 单精度: float 双精度: double 字符串类型
2、默认形参
1、在声明函数时,可以给函数的形式参数设置一个默认值,当调用函数时,设置过默认值的参数位置可以不提供实参,会使用默认值。
#include <iostream> using namespace std; void func(int num1=1234,int num2) { cout << num << endl; } int main(int argc,const char* argv[]) { func(123); return 0; }
2、设置默认值的参数要连续且靠右,因为调用者提供的实参要优先提供给没有设置默认的参数使用。
// 错误写法 void func(int num1,int num2=1234,int num3=456) { cout << num1 << " " << num2 << " " << num3 << endl; } int main(int argc,const char* argv[]) { func(1,2,3); return 0; }
3、如果函数的声明和定义分开实现,则只能在声明函数时设置默认形参,定义函数时不能设置默认形参,因为没有意义。
void func(int num1,int num2,int num3=1234); int main(int argc,const char* argv[]) { func(1,2); return 0; } void func(int num1,int num2,int num3) { cout << num1 << " " << num2 << " " << num3 << endl; }
函数设置默认形参可能会造成调用时的冲突,如果该函数进行重载,又对部分函数设置的默认形参,就可能导致调用函数时有多个备选方案,造成调用冲突。
void func(int num1,int num2) { cout << num1 << " " << num2 << endl; } void func(int num1,int num2,int num3=1234) { cout << num1 << " " << num2 << " " << num3 << endl; } int main(int argc,const char* argv[]) { func(1,2); return 0; }
3、内联函数:
C++标准委员会设计一种特殊函数,函数在声明时在返回值类型的前面增加 inline 关键字,当调用该函数时,编译器不会生成跳转指令,而是函数的二进制指令直接拷贝到调用位置,这样执行该函数时,直接执行二进制指令,不需要跳转,也不需要返回,所以执行速度比普通函数快,就像宏函数,但过多使用会造成冗余,代码段变大。
注意:内联函数只是C++标准委员会设计方案,具体是否内联要看编译厂商是否实现了内联功能。
编译器优化:
gcc/g++ -On 设置编译器的优化级别
默认情况下是-O0,不进行内联,-O2以上才会进行内联。
与宏函数的相同点和不同点:
相同点:
没有跳转、返回过程,提高代码执行速度,都会造成过多使用会造成冗余,代码段变大。
不同点:
1、内联函数会检查参数的类型和个数,宏函数只会检查参数的个数而不会检查类型,内联函数比宏函数安全。
2、宏函数提供任何类型的参数都可以调用,但内联函数的实参只能使用部分类型,宏函数比内联函数的通用性强。
3、内联函数可以有返回值,而宏函数没有。
什么样的函数适合设置为内联函数:
与宏函数一样,请参考C语言的笔记。
注意:结构、联合、类的成员函数默认都设置为了内联函数,这种内联被称为隐式内联,使用inline修饰的函数被称为显式内联。
五、堆内存管理的区别
C语言的内存管理:
1、C语言中没有堆内存管理的语句,而是在C标准库中提供了一套堆内存管理的函数,在使用时还需要包含stdlib.h头文件。
2、malloc系列函数申请堆内存时需要提供字节数,可能会出现字节数不够或过多的情况。
3、malloc系列函数返回的是void类型的地址,如果想在C++中继续使用malloc系列函数,则必须对返回值进行强制类型转换,原因就是在C++中void类型的地址不能再自动转换成其它类型的地址。
4、malloc系列函数不能对申请到的内存设置初值,只有calloc函数可以把申请到的内存初始化0,或者之后调用memset、bzero函数进行初始化。
5、在C++中malloc系列函数为结构、联合、类对象申请、释放内存时,不会自动调用构造、析构函数。
6、在申请数组型的内存块时,可以使用malloc或calloc,释放时也使用free函数。
7、malloc系列函数申请内存失败时会返回NULL地址。
C++的堆内存管理:
1、C++中有堆内存管理的语句,可以new、delete运算符管理堆内存,直接使用不需要包含任何头文件。
2、new运算符在申请内存时只需要提供数据类型,它会自动计算所需要的字节数,每次申请的字节数都刚刚好,不多不少。
3、new运算符返回的是带类型的地址,申请时提供是什么类型,返回的地址就是什么类型的。
4、new在申请内存时,还可以对申请到的内存设置初值。
5、new/delete为结构、联合、类对象申请、释放内存时,会自动调用构造、析构函数。
6、在申请数组型的内存块时,使用new 类型[n],释放内存时使用delete[],不能与new/delete不能混用,因为new[]/delete[]会自动调用n次构造、析构函数,而new/delete只调用一次构造、析构函数。
7、当使用new申请内存失败时,会抛出std::bad_alloc异常,而不是返回空地址。
#include <iostream> #include <stdlib.h> using namespace std; struct Student { int id; char name[20]; short age; float score; Student(void) { cout << "构造函数" << endl; } ~Student(void) { cout << "析构函数" << endl; } }; int main(int argc,const char* argv[]) { int* p = new int(123456); cout << *p << endl; delete p; /* Student* stup = new Student; delete stup; */ /* Student* stup = (Student*)malloc(sizeof(Student)); free(stup); */ /* Student* stus = new Student[~0]; delete[] stus; */ return 0; }
new/delete和malloc/free的相同点:
1、都可以管理堆内存。
2、返回的都是地址,只是类型不同。
3、都必须配合指针变量使用。
4、delete和free都可以释放空指针,但都不能重复释放。
重点掌握面试题:malloc/free 和 new/delete 的区别? 身份: 函数 关键字/运算符 返回值: void* 对应类型的指针 参数: 字节个数(手动计算) 类型(自动计算) 连续内存:手动计算总字节数 new[个数] 扩容: realloc 无法直接处理 失败: 返回NULL 抛异常 构造\析构: 不调用 调用 初始化: 不能初始化 可以初始化 头文件: stdlib.h 不需要 函数重载: 不允许重载 允许 内存分配的位置: 堆内存 自由存储区 注意:自由存储区是一个抽象的概念,而不是具体某个位置段,平时一般称new是分配在堆内存也问题不大,因为new底层默认调用了malloc,所以此时称分配在堆内存没问题,但是new可以像运算符一样被程序员重载或借助 new(地址) 类型 两种方式分配内存时,可以分配到其他内存段,所以称为自由存储区 笔试题: 现在有一块已经分配好的内存(堆、栈) 如何让通过new新申请的类对象、结构变量使用这块内存
常考面试、笔试题:new/delete和malloc/free的相同点和不同点?
问题:delete[] 释放内存时为什么可以调用n次析构函数。
使用new[] 申请数组型的堆内存时(结构、联合、类对象),所申请内存块的前4个字节,记录着内存块的块数,所以使用delete []释放内存时可以准确调用n次析构函数。
Student* stus = new Student[13]; cout << *((int*)stus-1) << endl; delete[] stus;
问题:在C++中如何把已有一块内存(从操作系统获取到的内存,例如:ipc共享内存)分配给结构、联合、类对象,能自动调用构造、析构函数。
// new(内存地址) 类型; new运算符可以重新解释一块内存,并自动调用构造函数 int main(int argc,const char* argv[]) { void* ptr = malloc(sizeof(Student)); cout << ptr << endl; Student* stup = new(ptr) Student; cout << stup << endl; delete stup; return 0; }
六、引用和指针
什么是引用:
引用是一种取名机制,它可以给变量重新取一新的名字,所以引用也叫别名。
为什么使用引用:
1、跨函数共享变量,把函数的参数设置引用,可以在函数内共享实参变量,并且是否共享实参变量由函数的实现者决定,这种设计给了函数实现者权限,并且给了函数调用者方便。
#include <iostream> using namespace std; void func(int& dashixiong) { dashixiong = 5678; cout << &dashixiong << " " << dashixiong << endl; } int main(int argc,const char* argv[]) { int sunlingling = 1234; int& dashixiong = sunlingling; func(sunlingling); cout << &sunlingling << " " << sunlingling << endl; return 0; }
2、提高函数的传参效率,默认情况下,函数传参单向值传递,变量有多少个字节就要拷贝多个字节的数据,而传递变量的地址,可以需要拷贝4字节的数据,而使用引用一个都不需要拷贝,它比指针的传参的效率还要高。
#include <iostream> using namespace std; struct Data { char str[20]; int num; char ch; }; void func(Data& d) { } int main(int argc,const char* argv[]) { Data d; for(int i=0; i<200000000; i++) { func(d); d.num++; } return 0; }
使用引用要注意的问题:
引用在定义时必须初始化,所以不可能存在空引用,但可能存在悬空引用,但使用指针可能空指针、野指针,所以使用引用比使用指针安全。
#include <iostream> using namespace std; int& func(void) { int num = 1234; return num; } int main(int argc,const char* argv[]) { /* int& hehe = func(); cout << hehe << endl; */ int* p = new int(1234); cout << *p << endl; int& num = *p; delete p; cout << num << endl; return 0; }
可以引用常量数据、字面值数据,但需要定义const类型的引用。
int main(int argc,const char* argv[]) { const int num = 1234; const int& hehe = num; const int& xixi = 56789; cout << hehe << endl; cout << num << endl; cout << xixi << endl; return 0; }
函数的参数使用引用时,实参变量就有被修改的风险,为了防止实参变量被破坏,可以使用const修改引用。
void func(const int& num) { num = 23456789; } int main(int argc,const char* argv[]) { int num = 1234; func(num); cout << num << endl; return 0; }
可以引用一个数组(定义数组的引用),但不能定义引用型的数组。
int main(int argc,const char* argv[]) { int arr[5] = {1,2,3,4,5}; // 可以定义数组指针 int (*arr1)[5] = &arr; // 可以定义指针数组 int* arr2[5]; // 可以定义数组引用或引用数组 int (&arr3)[5] = arr; // 不可以定义引用型的数组 int& arr4[5]; return 0; }
总结:在C++中尽量多使用引用,少使用指针。
常考笔试、面试题指针和引用的相同点和不同点?
七、类型转换的区别
隐式类型转换:
C语言和C++语言隐式类型转换区别不大,仅的区别有:
1、整数数据不能再隐式转换成枚举。
2、void类型的指针不能再隐式转换成其它类型的指针。
强制类型转换:
1、C语言中的强制类型转换语法在C++中还可以继续使用,但官方不建议这样,因为有安全隐患。
2、C++中设计出一套新强制类型转换的规则:
reinterpret_cast<目标类型>(数据) static_cast<目标类型>(数据) const_cast<目标类型>(数据) dynamic_cast<目标类型>(数据)
3、虽然使用起来比较麻烦,没有C语言方便,但是它能检查程序员转换是否安全,并提示错误。
4、之所以设计这么复杂就是为了让程序员记不住,不使用强制类型转换,因为C++之父本贾尼·斯特劳斯特卢普认为好的代码设计就不应该使用强制类型转换,当需要强制类型转换时,他希望程序员去优化自己的代码不是使用强制类型转换。
八、操作符别名
C、C++语言中使用的运算符或符号,在个别地区的键盘上是没有的,为了让所有人都使用C++进行编程,所以就对个别的字符取了别名。
|| 等价于 or && 等价于 and { 等价于 <% } 等价于 %>
#include<iostream> using namespace std; int main (void) <% int i = 1,j = 0; if (i or j) <% cout << "true" << endl; %> else <% cout << "false" << endl; %> return 0; %>
C语言与C++语言的区别有哪些?
1、Hello代码的区别
2、数据类型的区别
基本类型、复合类型
3、函数的区别
函数重载、形参默认值、内联函数
4、堆内存管理的区别(malloc、free与new、delete的区别)
5、函数传参的区别,指针和引用
6、类型转换