C++入门
一. c++统一初始化方案
- 用花括号初始化一切,默认值为0;
int main(){
int a{10};
double b{};{0}
int *c{a};
int ar[]{1,2,3,4,5};
int br[10]{};//{0 0 ...}
int *e{};//null
bool f{};//false;
}
- 花括号初始化不可以隐式转换;
double a=12.23;
int b=a;//得到a为12
//int c{a};//报错X
二. c++输入输出
- 引入头文件
- cin输入流
- cout输出流,"<<"插入符。
#include<iostream>//输入输出流头文件
using namesapce std;
int main(){
char ch;
int a;
//cin,键盘
cin >> ch >>a;
//cout
cout << "a = "<< a <<"ch="<<ch<<endl;
return 0;
}
- cin字符串空格结束
#include<iostream>
int main(){
char s[20];
cin>>s;//
//zhangxuan hello
cout<<s;//zhangxuan
char a[20];
cin.getline(s,20,'#');
//zhangxuan hello
cout<<a;//zhangxuan hello
}
三.const和指针
- c语言const为变量,c++const为常量。
在c++中,编译阶段会将代码中的a全部变为常量5,而c语言认为其还是变量。
int main(){
const int a=5;
int s[a]={1,2,3,4,5};//c语言报错
int b=0;
int *p=(int *)&a;//强转
*p=100;
b=*p;//c语言为100,c++为5
cout<<b<<" "<<*p;
return 0;
}
- const和指针的使用。
int a=5;
int *b = &a;//可以更改指向和修改值
int const *c = &a;//只能修改指向
int * const d = &a;//只能修改值
int const * const e = &a;//都不能
- 指针指向时指针能力不能放大,否则不安全。
int a = 10;
const int *b=a;
//int *p1=b;//错误,b不能修改值,p1也不能修改值。
int const *p2=b;
//int * const p3=b;//错误,b不能修改值,p3也不能修改值
int const * const p4=b;
int a = 10;
int * const b=a;
//全部正确,因为b要求不能改指向,与其他是否改指向无关。
int *p1=b;
int const *p2=b;
int * const p3=b;
int const * const p4=b;
四. 引用(别名)
- 引用相当于别名,不能修改引用指向。
int a=5;
int &c=a;
cout<<a<<" "<<c;//5 5
c++;
cout<<a<<" "<<C;//6 6
cout<<&a<<" "<<&c;//相同地址
- 定义必须初始化,不能空引用
int a=5;
int &c;//错误,会报错,因为空引用
- 没有引用的引用
int a=5;
int &b=a;
int &&c=b;//错误,&&为右值引用
int &c=b;
- 函数传参数时使用
void Swap_Int(int *a,int *b){
assert(a!=NULL&&b!=NULL)
int *p=a;
*a=*b;
*b=*p;
}
//相当于别名,a和b为x和y的别名
void Swap_int(int&a,int&b){
//没有空引用,无需判断
int p=a;
a=b;
b=p;
}
int main(){
int x=5,y=10;
Swap_Int(&x,&y);
Swap_int(x,y);
}
-
const和引用
- const修饰&,只能读不能改。
int a=5; const int & b=a; b++;//错误,被const修饰,不能修改指向。 int c=b;
- const修饰引用,能读能改,系统会忽略const,因为引用本身不能改指向。
int a=5; int & const b=a; b++; int c=b;
- 引用过程中能力不能放大。
const int a=5; int &b=a;//错误,不安全 const int &c=a; int d=5; int &e=d; const int &f=d; const int &g=100;//底层为下述实现 //tmp =100 //const int &g=tmp
五. 指针和引用区别
-
语法规则上的区别
- 指针变量存储的某个实例的地址,而引用是某个实例的别名。
- 程序为指针变量分配内存区域,而不为引用分配内存区域。
- 指针使用要加‘*’解引用,引用直接使用。
- 指针变量的值可以改变,来存储其他实例的地址,引用在定义时被初始化,之后无法改变。
- 指针可以为空(NULL),引用没有空引用。
- 指针变量作为形参需要检测其合法性(判空NULL),引用不需要判空。
- sizeof得到的指针变量的大小,而引用得到数据值的大小。
- 指针级数没有限制,可以有指针的指针,引用不存在引用的引用,只有一级引用。
- ++指针改变的指向,++引用改变的值。
-
本质上的区别
- 在编译阶段,程序会将引用改为常性指针。
int &a=b; int *const a=&b;//进行如下替换
六. inline内联函数
当函数开销小于开栈清栈开销使用inline,大于开栈清栈使用普通函数。
-
当代码行数小的时候,将代码直接放入调用处,不用建立栈空间,保护现场等操作。
-
inline是一种以空间换时间的方法,省去了调用函数的开销,但当函数过长时或者是递归函数时即使加inline也不会展开。
-
定义和声明分开后会出错,所以内敛函数定义和声明不能分离。
-
使用需要设置配置信息:项目-属性-C/C++
- 优化-内敛函数扩展-inline。
- 常规-调试信息格式-数据程序库。
inline int add(a,b){
return a+b;
}
int main(){
int a=5,b=10;
//函数在底层执行时会将函数展开
//cout<<a+b<<endl;
cout<<add(a,b)<<endl;
return 0;
}
七. 缺省函数
在函数定义时给出缺省值,当函数给出实际值时改为实际值,如果没给出实际值使用缺省值,C语言不支持。
- 缺省顺序必须从后往前
void func(int a,int b=5,int c=10,int d=15){
cout<<a<<" "<<b<<" "<<c<<" "<<d;
}
void func(int a,int b=5,int c,int d)//错误
- 函数的调用参数必须从左往右给
int main(){
fun(1,2,3,4);//1,2,3,4
fun(1,2,3);//1,2,3,15
fun(1,2);//1,2,10,15
fun(1,2,,4);//错误
}
- 在多文件中,声明中给缺省值,定义不要给缺省值
//a.h
void func(int a,int b,int c=5,int d=10);
//a.cpp
#include "a.h"
void func(int a,int b,int c,int d){
cout<<a<<" "<<b<<" "<<c<<" "<<d;
}
//main
#include "a.h"
int main(){
func(1,2,3);
}
- 形参可以为函数或者变量
int my_rand()
{
srand(time(NULL));
return rand()%10;
}
void func(int a,int b=my_rand()){
cout<<a<<" "<<b<<endl;
}
八. 函数重载
C++可以函数同名,只要参数类型不同,或者类型相同但个数不同,就称为函数重载。
a. 七个无法重载的情况
- 函数名相同,参数表相同,返回类型不同,并不认为函数重载,即使名字粉碎后不同,但主函数无法区分。
double my_max(int a,int b){}
int my_max(int a,int b){}
my_max(5,10);//.并不能进行区分
- 参数表的比较过程于形参名无关。
void print(int *b,int n){}
void print(int *s,int len){}
print(&a,5);//并不能进行区分
- 参数表的比较过程与缺省实参无关。
int my_max(int a,int b=10){}
int my_max(int a,int b){}
my_max(5,10);//并不能进行区分
- 参数类型重定义但本质相同的不能算重载。
typedef unsigned int U_int;
void func(U_int a){}
void func(unsigned int b){}
func(10);//不能进行区分
- 如果形参是按值传递的,参数表比较时会忽略const和volatile。
void func(int a){}
void func(const int a){}
func(10);//无法区分
- 如果形参是按指针或引用传递的,参数表比较时const和volatile可以进行区分。
void func(int *p){}
void func(const int*p){}
void func(int &p){}
void func(const int&p){}
int *a;
const int *b;
int c=10;
func(a);
func(b);//可以区分
func(c);
func(10);
- 函数重载尽量不要使用缺省值,否则无法区分会报错。
void func(int a){}
void func(int a,int b=5){}
func(15);//无法区分
b. 重载与常性参数
普通指针或引用可以调用常性参数,但常性参数不能调用普通参数。
- 指针
void fun(int *p){}//a
void fun(const int *p){}//b
//void fun(int *const p){}//c
//a与c相同,无法正确编译,因为指针的传递本身就无法改变原指针的指向。
int a=5;
const int b=10;
fun(&a);//当a存在时调用a,当a不存在时调用b。
fun(&b);//当b存在时调用b,当b不存在时报错
- 引用
//void func(int p){}//a
void func(int &p){}//b
void func(int const &p){}//c
//编译时不会出错,但运行时出错,因为无法区分a和b。
int a=5;
const int b=10;
func(a);//当b存在时调用b,当b不存在时调用c。
func(b);//当c存在时调用c,否则报错
c. 重载的原因
名字粉碎(名字修饰):c或c++在函数内部通过修饰符给函数重命名为修饰名,然后通过重命名后的名称区分函数,修饰名是编译器在编译函数定义或者原型时生成的字符串。
- __cdecl:默认修饰符。
- __stdcall:标准修饰符。
- __fastcall:快速修饰符。
- c语言
- 默认修饰符在函数前加下划线。
- 标准修饰符在函数前加下划线,在下划线后加@形参字节大小
void max(int a,int b){}
//默认修饰方法 粉碎为_max
void __stdcall max(double a,double b){}
//粉碎为_max@4
- c++语言
-
?+函数名+@@YA+参数表+@Z。
-
默认调用为@@YA,标准调用为@@YG
-
参数表第一项为返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前。
-
如果没有参数,以Z结尾而不是@Z。
-
//参数表信息 X-void D-char E-unsigned char F-short H-int l-unsigened int j-long K-unsigned long M-float N-double _N-bool PA-指针,后面代码表明指针类型,如果同类型连续出现,以“0”代替,一个“0”代表一次重复。
//?my_max@@YAHHH@Z
int __cdecl my_max(int a,int b);
//?my_max@@YGDDD@Z
char __stdcall my_max(char a,char b);
//?my_max@@YANNN@Z
double my_max(double a,double b);
在c++环境下,用extern ”C“可将函数作为C方式重命名。反过来不可以,因为c++在设计上兼容c,c不兼容c++。
extern "C"
{
void fun(int a){}
void print(){}
}
九. 函数模板
函数模板可以创建一个通用功能的函数,以支持多种形参,简化重载函数的设计。
template<class T>
T my_max(T a,T b){
cout<<typeid(T).name<<endl;
return a>b?a:b;
}
int main(){
int x=my_max(12,23);
int y=my_max<int>(12,'a');//直接指定类型为int,不用推演。
char ch=my_max('a','b');
double dx=my_max(34.45,12.23);
}
函数模板是生成代码的代码,当遇见指定类型调用时,会进行推演然后生成指定类型的代码,再进行编译和链接,并不是简单的将T替换为指定类型。
//当遇见my_max(12,23)时,编译器生成下列代码
typedef int T;
T my_max<int>(T a,T b){
cout<<typeid(T).name<<endl;//打印int
return a>b?a:b;
}
//再进行编译链接
十. 名字空间
解决名字污染问题,防止出现名字冲突。
- 普通名字空间域
namespace zx{
int g_max=10;
int g_min=0;
int my_add(int a,int t){
return a+b;
}
}
//相同名称的会合并
namespace zx{
double pi=3.14;
}
- 命名空间套命名空间
namespace ldd{
double pi=3.1415926;
double my_add(double a,double b) {
return a+b;
}
namespace Martrix{
int c=10;
char my_max(char a,char b){
return a+b
}
}
}
- 多文件在.cpp内也要写命名空间。
//a.h
namespace zx{
int add(int a,int b);
}
//a.cpp
#include"a.h"
namespace zx{
int add(int a,int b){
return a+b;
}
}
//main
#include"a.h"
using namespace std;
int main(){
cout<<add(5,10)<<endl;
}
- 作用域解析符“::”(作用域限定符)
int main(){
int a=zx::my_add(10,20);
cout<<ldd::pi<<endl;
cout<<ldd::Martrix::c<<endl;
}
- using声明
- 声明作用域内的成员
using zx::pi;
using ldd::Martrix::my_max;
int main(){
cout<<pi<<endl;
cout<<my_max('a','b');
}
- 声明整个命名空间
using namespace zx;
int main(){
cout<<pi<<endl;
cout0<<add(5,10)<<endl;
}
十一.new/delete
空间申请区域有上越界标记和下越界标记,都是一个字节,fdfdfdfd填充。
- C语言的内存分配
- malloc,申请空间。
- realloc,扩充空间,c++没有填充。
- calloc,申请空间,用0填充。
- free释放空间。
int n=10;
int *ip1=(int *)malloc(sizeof(int));//用cdcd填充
int *ip2=(int*)calloc(n,sizeof(int));用0填充
ip2=(int*)realloc(ip2,sizeof(int)*n*2);//内存扩大到第二个参数。
free(ip1);
ip1=nullptr;
free(ip2);
ip2=nullptr;
-
C++的内存分配
-
new开辟内存
-
申请空间
-
初始化
- 初始化个数需要声明个数相同,否则初始化会占用下越界标记。
int m=2; int *ip=new int[m]{1,2,3}; //fdfdfdfd //01000000 //02000000 //03000000,本来应该是fdfdfdfd //其他
-
-
new的函数使用
int *IP=(int *)::operator new(sizeof(int));//与malloc等价 ::operator delete(ip1);//与free等价
- 定位new的使用
对已经申请的空间进行初始化。
int n=10; int *ip1=(int *)malloc(sizeof(int)); int *ip2=(int *)::operator new(siezof(int) *n); new(ip1)int{10};//定位new new(ip2)int[]{1,2,3,4,5,6};
- delete
- delete IP1,删除一个空间。
- delete []IP2,删除一组空间。
-
int n=10;
int *ip1=new int(10);//申请一个初始化为10
int *ip2=new int[10];//申请十个int,初始化为cdcd
int *ip3=new int[10]{1,2,3,4,5,6};//申请十个int,初始化为1,2,3,4,5,6,其余为0
delete ip1;
delete []ip2;
delete []ip3;
- c和c++内存分配区别
- 对于内置类型没有区别。
- new/delete是运算符,malloc/free是函数。
- new自动计算大小,malloc手动计算大小。
- new可以初始化,malloc不能初始化。
- malloc返回值为void*,需要强转才能使用,new不需要。
- malloc申请失败返回的为NULL,new申请失败抛出异常。
十二. c++11的auto自动类型推导0
- auto定义的初始化必须给值,因为auto只是占位符。
auto x=10;//10>>X=int
auto dx=12.23;//12.23>>dx=double
auto fx=12.23f;//12.23f>>fx=float
auto ch='a';//'a'>>ch=char
const auto *xp=&a;//auto为int
auto ip=&x;//auto为int*
auto*sp=&x;//auto为int
//auto x=5,y;//报错,必须给初始值
//auto x=&x,y=6.0;//报错,出现二义性
- auto同指针和引用结合时,会考虑const限定符。
const int a=10;
auto b=a;//auto为int
auto *p=&a;//auto为const int
auto &c=a;//auto为const int
- 函数参数为auto,类似于函数模板。
void func(auto x){
cout<<typeid(x).name()<<endl;
cout<<x<<endl;
}
func(12);
func(12.23);
func('a');
- 无法推演数组的类型。
auto dr[10]={1,2,3,3,4,5};//报错
int ar[10]={1,2,3,4};
auto dr[10]=ar;//报错
int ar[10]={1,2,3,4};
auto &s=ar;//可以,甚至可以推出大小
- 可以推演函数返回值
template <class T>
T void(T a, T b){
return a+b;
}
auto sum=void(5,10);
auto sum0=void(1.2,2.3);
十三. decltype关键字
-
推演表达式的类型
decltype(表达式),不计算表达式,只说出表达式的最终类型。
int x=10;
decltype(x) y=1;//int
decltype(x+y) z=10;//int
double dx=10;
decltype(x+dy) c;//double类型
const int a=5;
decltype(a)p;//const int
return 0;
十四. 基于范围的for循环
- 只能遍历数组和容器
int ar[]={1,2,3};
int *ip=ar;
int (&ipa)[]=ar;
auto &ipd=ar;
for(auto:ipa){}//报错,不知道数组的大小
for(auto:ipd){}//正确,auto可以推出数组的大小
for(auto:ip){}//报错
//格式
for(ElemType val:array){
//循环体
}
int main(){
int ar[]={1,2,3,4,5,6,7,8,9};
for(auto i:ar){
cout<<i<<",";
//1,2,3,4,5,6,7,8,9
}
for(int i:ar){
cout<<i<<",";
i+=10;
//1,2,3,4,5,6,7,8,9
//因为每次将数组的元素赋值给i,所以对结果没有影响
}
for(int &i:ar){
i+= 10;//以引用方式,可以改变值
}
for(const auto &x:ar){
i+=10;//报错,const修饰
}
}
十五. 指针空值–nullptr
由于c++对类型限制比较强,对于下述代码:
void *ip=0;
int *a=ip;
C语言可以编译通过。因为进行了隐式转换,C++不允许此处进行隐式转换,必须强制类型转换。
void *ip=0;
int *a=(int *)ip;
如果c++将NULL宏定义为((void *)0),则会出现下面情况:
int *a=(int*)NULL;
char *b=(char*)NULL;
double *c=(double*)NULL;
所以c++底层将NULL定义为0.
但如果出现下列代码
void fun(int a){};
void fun(char *p){};
fun(0);
fun(NULL);
很显然两个fun都调用第一个函数,与NULL调用参数为指针的fun函数不否,为了解决这个问题,c11引入nullptr指针类型空值常量,可以给一切指针赋值,但不能给其他类型赋值。
- c语言
#define NULL ((void*)0)
- c++
#define NULL 0
char *ip=nullptr//指针空值类型常量,指针零值,可以向一切指针赋值
sizeof(nullptr)==sizeof((void*)0);//相同
int a=nullptr;//错误,类型不同,指针空值类型和int类型。
nullptr可以给任何指针赋值,表示指针空值类型。
十六. typedef和using
- typedef和using都可以重命名
typedef unsigned int u_int32;
using u_int32 = unsigned int;
- using可以和模板结合使用
template<class T>
using pointer=T*;
int main(){
int a=5;
pointer<int>p=&a;
char ch='a';
pointer<char>cp=&ch;
}
- using可以声明名字空间
using namespace std;