02_C++和C
1.c和c++关系
2. 区别
2.1 小特性
整体来说C++兼容了C语言的语法,并且做了很多扩展
- C++支持在使用变量的时候再定义。
- C++支持访问register 地址,实际会优化为无效。
- C++不允许定义多个同名的全局变量
#include <stdio.h>
int g_v;
//int g_v; //3
int main(int argc, char *argv[])
{
printf("Begin...\n");
int c = 0;//1
for(int i=1; i<=3; i++)
{
for(int j=1; j<=3; j++)
{
c += i * j;
}
}
printf("c = %d\n", c);
register int a = 0;//2
printf("&a = %p\n", &a);
printf("End...\n");
return 0;
}
2.2 struct 关键字 & 函数声明
struct关键字的加强方面
- C语言中的
struct
定义了 一组变量的集合其中的变量标识符并不是一种新的类型; - C++ 中的
struct
可以用于定义一个全新的类型不需要使用类似typedef
的机制来说明这个新的类型;
函数声明
- C++中所有的表示符都必须显示声明类型;
- C语言中的默认类型在C++都是不合法的(缺省的
int
,void
…);
typedef struct _student student;
struct _student
{
const char * name;
int age;
};
struct student
{
const char * name;
int age;
};
#include <stdio.h>
//C++认为是一个类型是可以实例化的,C不支持
struct Student
{
const char* name;
int age;
};
f(i)
{
printf("i = %d\n", i);
}
g()//C中可以接受任何参数,返回值为默认的`int`类型,C++不可以
{
return 5;
}
int main(int argc, char *argv[])
{
Student s1 = {"Delphi", 30};
Student s2 = {"Tang", 30};
f(10);
printf("g() = %d\n", g(1,2,3,4,5));
return 0;
}
int f1() & int f2(void)
区别?
使用不同的编译器(C不同。C++相同)
2.3 const
C语言中const
const
修饰的变量是只读,本质上还是一个变量const
修饰的局部变量在栈上分配空间const
修饰的全局变量在只读存储区分配空间const
只在编译期间有效,运行期间无效
总结:
只是该变量具有只读属性,无法作为左值修改(但是可以指针修改),所以并不是真正的常量,并且具有存储空间。
C定义常量:#define
enum
C++ const
在C++中遇到const
时,在符号表中生成常量
编译器期间遇到使用const
修饰的常量,直接使用之前符号表中的常量替换
编译器还是有可能为const
修饰的常量分配空间的,例如使用extern
关键字,或者使用&
符号。
但是不会使用期存储空间的值(不论修改与否,该常量还是保持之前的不变)目的是兼容C。
#include <stdio.h>
//GCC
int main()
{
const int c = 0;
int* p = (int*)&c; //c++ 还是会分配空间
printf("Begin...\n");
*p = 5; //c++ 此处和C一样都是5
printf("c = %d\n", c); //gcc 中 c = 5 g++中 c=0
printf("End...\n");
return 0;
}
类似效果和C中的define
差不多
但是又比宏高级 具备类型检查和作用域检查,而宏是预编译期间文本替换。
#include <stdio.h>
void f()
{
#define a 3
const int b = 4;
}
void g()
{
printf("a = %d\n", a);//编译器会预编译中展开为3
//printf("b = %d\n", b); // 由于在f()中作用域,执行会报错
}
int main()
{
const int A = 1;
const int B = 2;
int array[A + B] = {0};
int i = 0;
for(i=0; i<(A + B); i++)
{
printf("array[%d] = %d\n", i, array[i]);
}
f();
g();
return 0;
}
2.4 BOOL类型
C++中 bool
类型只有true
(编译器-1)和false
(编译器-0)
占用 1byte空间,能够支持数学运算 ,运算结果只有true
和false
,输出结果只有1和0
C中没有该类型,用int类型替代。
g++
#include <stdio.h>
int main(int argc, char *argv[])
{
bool b = false;
int a = b;
printf("sizeof(b) = %d\n", sizeof(b));//1
printf("b = %d, a = %d\n", b, a);/0,0
b = 3;
a = b;
printf("b = %d, a = %d\n", b, a);//1,1
b = -5;
a = b;
printf("b = %d, a = %d\n", b, a);//1,1
a = 10;
b = a;
printf("a = %d, b = %d\n", a, b);//10,1
a = 0;
b = a;
printf("a = %d, b = %d\n", a, b);//0,0
return 0;
}
2.5 三目运算和 refrence &
C++相对于C进行了升级
int a = 1;
int b = 2;
(a<b?a:b) = 3; c编译器不通过,c++中 a=3;b=2;但是必须是变量返回,不可以变量常量混搭、
引用 &
为一个已知的变量重新取一个别名
type namea =value;
type& nameb = namea;
注意:类型名字必须一致。并且必须在声明的时候初始化,绑定一个类型一致的变量。
根据内存模型,一个变量实际是物理内存上一个的sizeof(type)
的空间 在程序中可见的名称我们进行了一个独一无二的区分ID 叫做namea
,同样我们还可以取另外一个ID->nameb
来绑定这段内存,操作nameb
其实和操作namea
同样效果
#include <stdio.h>
int main(int argc, char *argv[])
{
int a = 4;
int& b = a;
b = 5;
printf("a = %d\n", a); //5
printf("b = %d\n", b); //5
printf("&a = %p\n", &a); //0xbfe54aec
printf("&b = %p\n", &b);//0xbfe54aec
return 0;
}
C++对于三目运算的返回 有两种情况:
- 全部是变量,返回变量的引用
- 含有或全部为常量,都返回常量值。所以不可以再接赋值操作。
2.6 引用本质
数值交换
//引用版本
void swap(int& a,int& b)
{
int t = a;
a = b;
b = t;
}
//指针版本
void swap(int *a,int*b)
{
int t = *a;
*a = *b;
*b = t;
}
引用在传参的时候进行初始化。
const 引用:
作用:让变量具有只读属性
#include <stdio.h>
void Example()
{
printf("Example:\n");
int a = 4;
const int& b = a;
int* p = (int*)&b; //p= &a
//b = 5;//error b是只读变量
*p = 5;//ok
printf("a = %d\n", a);
printf("b = %d\n", b);
}
//c++ 使用常量对const 引用初始化时候,c++ 会为常量值分配存储空间,并量引用名称作为这段空间别名。
void Demo()
{
printf("Demo:\n");
const int& c = 1; //ok
int* p = (int*)&c;
//c = 5;//error
*p = 5;//ok
printf("c = %d\n", c);
}
int main(int argc, char *argv[])
{
Example();
printf("\n");
Demo();
return 0;
}
引用是否有自己的存储空间?
#include <stdio.h>
struct TRef
{
char& r;
};
int main(int argc, char *argv[])
{
char c = 'c';
char& rc = c;
TRef ref = { c };
printf("sizeof(char&) = %d\n", sizeof(char&)); //1
printf("sizeof(rc) = %d\n", sizeof(rc)); //1
printf("sizeof(TRef) = %d\n", sizeof(TRef));//4
printf("sizeof(ref.r) = %d\n", sizeof(ref.r));//1
return 0;
}
从上可知,引用本质上占用4byte 和指针一致。
- C++ 编译器在编译过程中,用指针常量作为引用内部的实现方式,因此占用相同空间;
- 从使用的角度来说,引用只是一个别名,隐藏了指针操作的具体细节;
#include <stdio.h>
struct TRef
{
char* before;
char& ref;
char* after;
};
int main(int argc, char* argv[])
{
char a = 'a';
char& b = a;
char c = 'c';
TRef r = {&a, b, &c};
printf("sizeof(r) = %d\n", sizeof(r));//12
printf("sizeof(r.before) = %d\n", sizeof(r.before));//4
printf("sizeof(r.after) = %d\n", sizeof(r.after));//4
printf("&r.before = %p\n", &r.before);//0xbf8a300c+4 = 0xbf8a3010
printf("&r.after = %p\n", &r.after);//0xbf8a3014
return 0;
}
使用引用替代指针操作根据安全性和易用性。注意:不能返回局部变量的引用
#include <stdio.h>
int& demo()
{
int d = 0;
printf("demo: d = %d\n", d);
return d; //warnning
}
int& func()
{
static int s = 0;
printf("func: s = %d\n", s);
return s;
}
int main(int argc, char* argv[])
{
int& rd = demo();
int& rs = func();
printf("\n");
printf("main: rd = %d\n", rd); //wrong
printf("main: rs = %d\n", rs);
printf("\n");
rd = 10; //wrong 不能修改
rs = 11;
demo();
func();
printf("\n");
printf("main: rd = %d\n", rd);
printf("main: rs = %d\n", rs);
printf("\n");
return 0;
}
2.7 inline
C语言中常使用#define
来定义常量数字,或者宏代码块,在预编译期间会替换相对应的文本#define a 5
;
C++ 中对于常量的使用除了兼容#define
以外还有cons
t ,例如 const int a = 5
;
相比于#define
没有类型检查,仅仅是替换文本,使用的时候必须谨慎,而const
常量可以替换宏常数;
C++ 对于宏代码块,使用内联函数:
inline int function(int a,int b)
{
return a>b? a:b;
}
- C++ 编译器将把用
inline
声明的函数体插入到函数调用的地方; - 内联函数没有普通变量调用时的额外开销,并且具有函数的检查特性(编译阶段的参数检查以及返回检查)
- C++ 编译器不一定满足内联函数的请求,编译器可以拒绝请求(
inline
只是声明我们请求把整个函数作为内联,编译器不一定实现); - 效率基本和`#define 差不多并且更加安全
#include <stdio.h>
#define FUNC(a, b) ((a) < (b) ? (a) : (b))
inline int func(int a, int b)
{
return a < b ? a : b;
}
int main(int argc, char *argv[])
{
int a = 1;
int b = 3;
int c = FUNC(++a, b);//int c = ((++a) < (b) ? (++a) : (b)) ----> 2<3? 3 目的是2?
int c = func(++a, b);
printf("a = %d\n", a);//define 3 inline2
printf("b = %d\n", b);//define 3 inline3
printf("c = %d\n", c);//define 3 inline2
return 0;
}
以上可知使用#define
定义红代码块的时候存在着副作用。然使用inline
不会。
一般根据编译器的优化选项来确认是否使用inline
具体可以反汇编查看编译后的结果,现在编译器 也有可能会因为优化抉择把函数优化为内联函数 。当然也可以使用编译选项,强制声明为内联函数:
//__forceinline //MSVC
//__attribute__((always_inline)) //G++
#include <stdio.h>
//__forceinline //MSVC
//__attribute__((always_inline)) //G++
inline
int add_inline(int n);
int main(int argc, char *argv[])
{
int r = add_inline(10);
printf(" r = %d\n", r);
return 0;
}
inline int add_inline(int n)
{
int ret = 0;
for(int i=0; i<n; i++)
{
ret += i;
}
return ret;
}
inline函数被编译器内联限制:
- 不能存在任何循环语句
- 不能存在过多的条件判断
- 函数体不能过于庞大
- 不能对函数进行 & 操作
- 必须在调用前声明
以上也不是一定,根据编译器的决定,具体汇编代码分析
2.8 C++ 函数参数默认值
如果函数在调用的的时候,没有传入新的参数的,会默认使用函数声明时候的默认值(默认形参构造),若有新的参数 会优先使用新的参数;
#include <stdio.h>
int mul(int x = 0);
int main(int argc, char *argv[])
{
printf("%d\n", mul()); //0
printf("%d\n", mul(-1));//1
printf("%d\n", mul(2));//4
return 0;
}
int mul(int x)
{
return x * x;
}
对于多个默认参数的函数。必须使用从右到左依次声明默认参数初始化;
例如int add(int x, int y = 1, int z = 2);
是正确的;
int add(int x, int y = 1, int z );
此时不对;
#include <stdio.h>
int add(int x, int y = 0, int z = 0);
int main(int argc, char *argv[])
{
printf("%d\n", add(1)); // x = 0;y = 0;z= 0;
printf("%d\n", add(1, 2)); // x = 1;y = 2;z= 0;
printf("%d\n", add(1, 2, 3)); // x = 1;y = 2;z= 3;
return 0;
}
int add(int x, int y, int z)
{
return x + y + z;
}
如上,传入参数的若有新的参数,更新参数,若无的话,使用默认参数。
可以使用默认参数结合占位操作结合,兼容c语言的一些语法
#include <stdio.h>
int func(int x, int = 0);
int main(int argc, char *argv[])
{
printf("%d\n", func(1));
printf("%d\n", func(2, 3));
return 0;
}
int func(int x, int)
{
return x;
}